NML -- The ultimate Lambda for Scientific Programming
 
 
Arrays in NML are stored in memory as contiguous vectors of double-precision floating point values. They are all unboxed values. In the parlance of ordering, they are stored in row-major order, meaning that the rightmost index of a multidimensional array changes most rapidly as we access successive array elements.
 
Complex values are stored as alternating real and imaginary components, as opposed to separate memory regions containing purely real and imaginary components.
 
Every item in NML can be syntactically considered an array of at least one element. Every array in NML can be accessed simultaneously as though it were a 1-dimensional vector, and also as a multidimensional array of whatever dimensions it was created with. The reason we do this is because most vectorized math operators in NML perform element by element operations.
 
So for example, if a were a multidimensional array, we could compute the sine of every element by simply performing:
 
sin a
 
The result would be another array, with the same dimensions as argument a, but with elements whose values are the sines of corresponding elements of a.
 
However, when combining two arrays under some operator, e.g., addition or multiplication, both array arguments must have the same dimensions. So if a and b were two arrays with the same dimensions, we could multiply corresponding elements to produce a third array with the same dimensions, and whose elements are the product of corresponding elements from a and b:
 
let c = a * b
 
In NML we can access array elements by means of the dotted-bracket notation:
 
a.[15]
 
represents the 16th element of a, regardless of a’s dimensionality and rank. Indexing progresses from zero for the first element in each dimension. (Rank here refers to the number of dimensions, whether or not the array, considered as a matrix or tensor actually has that mathematical rank.)
 
If a were created originally as a 3x3 array:
 
let a = Array.create [3,3]
 
then a.[15] would be the same element as a.[6], which is the same element as a.[2,0].
 
When arrays are initially created, their contents are initialized to zero by default. But you can specify your own initialization constant by means of an extended syntax version for Array.create:
 
let a = Array.create([3,3], init: 1.0)
 
That argument list following Array.create is a tuple containing three items -- the first is the list of dimensions, the second is a symbol $init, and the third is the numeric value 1.0. But the point here is that the function Array.create always takes only one argument -- either a list of dimensions, or a tuple with more information.
 
Lists in NML are denoted by square-brackets, and the items are comma separated. OCaml requires semicolons as list item separators. Wherever possible, NML prefers comma separators.
 
Symbols in NML are denoted by names prefixed by the sharp-sign # or the dollar-sign $. Either one will do. Symbols are system-wide unique identifiers that can be used wherever convenient. When argument tuples contain colon-terminated names, as in the above argument tuple, it is really just a convenient shorthand used to label arguments. The tuple shown above is the same as the tuple:
 
([3,3], $init, 0.0)
 
And of course, like OCaml, we have an empty tuple () known as “unit”. There are no such things as tuples with one element, e.g., (e). Such syntax refers instead to a parenthesized expression.
 
In OCaml, dotted brackets are used for string element access, and dotted-parentheses are used for vector accessing. NML uses dotted brackets for all array, vector, string, list, and tuple references.
 
As mentioned previously, array indexing is cyclic in each dimension, so that two-dimensional arrays are unfolded views of a toroidal universe. Tiling is infinite in each axis.
 
Because we rely on OCaml’s BigArray type, there is a limit to the number of dimensions that can be had, but that limit is well beyond any practical applications that I am aware of -- unless you insist on the old-school Fortran approach of making everything in the universe into an array dimension. I believe the limit in NML and OCaml is 7 dimensions.
 
Arrays in NML are homogeneous in type -- all numbers. To accommodate mixed type collections we offer record types with labeled fields. Record fields can be declared mutable or not.
 
Lists, tuples, and vectors, containing only numbers can be coerced to arrays, and vice versa. While we just showed that lists are comma-separated items enclosed in square-brackets:
 
[3,3]
 
a vector, which corresponds directly with OCaml vectors, is denoted by the same syntax with a leading sharp-sign:
 
#[3,3]
 
Unlike OCaml, lists and vectors in NML can be composed of any variety of data types. In OCaml, lists and vectors must be homogenous in type. Not so in NML.
 
So it might seem redundant that NML offers all of List, Tuple, and Vector data types, and perhaps it is. They are almost interchangeable. And this might be a good place to trim the language back to essentials.
 
The real power of numeric array syntax shows up when we want to section, slice, and dice them...
Saturday, February 3, 2007
NML Arrays