Matrices

Matrices

Sun provides N-dimensional matrices through the standard library Matrix<T> class. Matrices support bracket indexing syntax and are backed by contiguous heap memory.

Using the Standard Library

Include stdlib.moon in your manifest:

manifest {
    libraries: ["stdlib.moon"]
}

Creating Matrices

Use Matrix<T>(allocator, shape) to create a matrix with the specified dimensions:

using std;
 
function main() i32 {
    var allocator = make_heap_allocator();
    
    // 1D array (vector) of 10 elements
    var v = Matrix<i32>(allocator, [10]);
    
    // 2D matrix (3x4)
    var m = Matrix<i32>(allocator, [3, 4]);
    
    // 3D tensor (2x3x4)
    var t = Matrix<f64>(allocator, [2, 3, 4]);
    
    return 0;
}
 
manifest {
    libraries: ["stdlib.moon"]
}

To initialize the elements as well, pass an array literal first and the allocator second. The matrix infers all dimensions and owns independent storage:

var m = Matrix<f32>([[1.0f32, 2.0f32], [3.0f32, 4.0f32]], allocator);
var value = m[1, 0];  // 3.0f32

This constructor also accepts an existing array by reference. Use elements of the matrix's type, such as Matrix<i64>([1i64, 2i64], allocator). Putting the values first keeps integer data distinct from the shape in Matrix<i64>(allocator, [2, 3]).

Indexing

Access elements using comma-separated indices in brackets:

using std;
 
function main() i32 {
    var allocator = make_heap_allocator();
    var m = Matrix<i32>(allocator, [3, 3]);
    
    // Assignment
    m[0, 0] = 1;
    m[1, 1] = 5;
    m[2, 2] = 9;
    
    // Reading
    var diag = m[0, 0] + m[1, 1] + m[2, 2];  // 15
    
    return diag;
}
 
manifest {
    libraries: ["stdlib.moon"]
}

For 1D matrices:

using std;
 
function main() i32 {
    var allocator = make_heap_allocator();
    var arr = Matrix<i32>(allocator, [5]);
    
    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;
    
    return arr[0] + arr[1] + arr[2];  // 60
}

Matrix Methods

get(indices) / set(indices, value)

Direct method access (equivalent to bracket syntax):

var val = m.get([i, j]);     // Same as m[i, j]
m.set([i, j], value);        // Same as m[i, j] = value

dim(i)

Returns the size of dimension i; ndims() returns how many dimensions there are. The matrix owns a copy of the shape it was built from.

function main() i64 {
    var allocator = make_heap_allocator();
    var m = Matrix<i32>(allocator, [3, 4]);
 
    println(m.dim(0));  // 3
    println(m.dim(1));  // 4
 
    return m.dim(0) * m.dim(1);
}

size()

Returns the total number of elements:

function main() i64 {
    var allocator = make_heap_allocator();
    var m = Matrix<i32>(allocator, [3, 4]);
    
    return m.size();  // 12
}

ndims()

Returns the number of dimensions:

function main() i64 {
    var allocator = make_heap_allocator();
    var t = Matrix<f32>(allocator, [2, 3, 4]);
    
    return t.ndims();  // 3
}

Matrix Views

MatrixView<T> provides a non-owning view into a Matrix<T> or another view. Views share memory with the original matrix.

Creating Views

Views are created by slicing a matrix. A view owns its own shape and strides and points at the matrix's elements:

function main() i32 {
    var allocator = make_heap_allocator();
    var m = Matrix<i32>(allocator, [9]);
    for (var i: i64 = 0; i < 9; i = i + 1) { m[i] = i; }
 
    // Elements 2, 3, 4 of m (shares memory with m)
    var view = m[2:5];
 
    return view[1] + view.dim(0);  // 3 + 3
}

Views do not own their data. The original matrix must remain valid while the view is in use.

How Operator Overloading Works

When you write m[i, j], the compiler translates it to method calls:

SyntaxCompiler Translation
m[i, j] (read)m.__index__([i, j])
m[i, j] = v (write)m.__setindex__([i, j], v)

To make your own class indexable, implement __index__ and __setindex__:

class MyArray {
    var data: ptr<i32>;
    var len: i64;
    
    init(allocator: ref HeapAllocator, size: i64) {
        this.data = allocator.alloc<i32>(size);
        this.len = size;
    }
    
    // Called for arr[i]
    method __index__(indices: ref array<i64>) i32 {
        return unsafe { _load<i32>(this.data, indices[0]); };
    }
    
    // Called for arr[i] = value
    method __setindex__(indices: ref array<i64>, value: i32) void {
        unsafe { _store<i32>(this.data, indices[0], value); };
    }
}
 
function main() i32 {
    var allocator = make_heap_allocator();
    var arr = MyArray(allocator, 10);
    
    arr[0] = 42;      // Calls __setindex__
    return arr[0];    // Calls __index__, returns 42
}

Complete Example

using std;
 
function main() i32 {
    var allocator = make_heap_allocator();
    
    // Create a 3x3 identity matrix
    var identity = Matrix<i32>(allocator, [3, 3]);
    
    // Initialize to zero
    for (var i: i64 = 0; i < 3; i = i + 1) {
        for (var j: i64 = 0; j < 3; j = j + 1) {
            identity[i, j] = 0;
        }
    }
    
    // Set diagonal to 1
    identity[0, 0] = 1;
    identity[1, 1] = 1;
    identity[2, 2] = 1;
    
    // Sum the diagonal
    var trace = identity[0, 0] + identity[1, 1] + identity[2, 2];
    
    return trace;  // 3
}
 
manifest {
    libraries: ["stdlib.moon"]
}

Memory Management

Matrix<T> uses an owning pointer (ptr<T>) for its data, which is automatically freed when the matrix goes out of scope. The allocator is only used during construction.

using std;
 
function createMatrix() i32 {
    var allocator = make_heap_allocator();
    var m = Matrix<i32>(allocator, [100, 100]);
    
    m[50, 50] = 42;
    return m[50, 50];
    
    // m is automatically freed here
}
 
manifest {
    libraries: ["stdlib.moon"]
}

GPU matrices

The optional NVIDIA library supplies cuda.DeviceMatrix<T>, explicit host/device transfers, and GPU arithmetic. Host std.Matrix<T> retains its existing storage and indexing behavior.