Matrices
The Matrix module provides two-dimensional matrix support for linear algebra, statistical computing, and numerical data processing. Matrices in Keel are homogeneous — each matrix holds values of one element type.
Element Types
Keel supports four matrix element types:
| Type | Description |
|---|---|
Matrix Float | Dense floating-point matrix (default for most constructors) |
Matrix Int | Integer matrix; use fromRowsInt, zerosInt, etc. |
Matrix Decimal | Arbitrary-precision decimal matrix |
Matrix (Maybe Float) | Sparse or nullable Float matrix |
The compiler rejects invalid element types at compile time. Matrix String or Matrix Bool are type errors.
Creating Matrices
import Matrix
let a = Matrix.fromRows [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
Matrix.shape a -- (2, 3)
Try itimport Matrix
-- Integer matrix
let b = Matrix.fromRowsInt [[1, 2], [3, 4]]
-- Identity matrix
let eye = Matrix.identity 2
-- From a flat list (column vector)
let v = Matrix.fromList [1.0, 2.0, 3.0]
Matrix.shape v -- (3, 1)
Try itShape and Access
import Matrix
let m = Matrix.fromRows [[10.0, 20.0], [30.0, 40.0]]
Matrix.shape m -- (2, 2)
Matrix.nrows m -- 2
Matrix.ncols m -- 2
Matrix.get 0 1 m -- Just 20.0 (row 0, col 1)
Matrix.get 5 0 m -- Nothing (out of bounds)
Matrix.toRows m -- [[10.0, 20.0], [30.0, 40.0]]
Matrix.toCols m -- [[10.0, 30.0], [20.0, 40.0]]
Matrix.toList m -- Ok [10.0, 20.0, 30.0, 40.0]
Try itOperators
Matrix operators follow standard mathematical notation:
| Operator | Meaning | Result type |
|---|---|---|
+ | Element-wise addition | Result (Matrix T) String |
- | Element-wise subtraction | Result (Matrix T) String |
* | Matrix multiplication | Result (Matrix Float) String |
.* | Hadamard (element-wise) product | Matrix T |
./ | Element-wise division | Matrix Float |
Dimension-mismatched operations return Err at runtime.
import Matrix
let a = Matrix.fromRows [[1.0, 2.0], [3.0, 4.0]]
let b = Matrix.fromRows [[5.0, 6.0], [7.0, 8.0]]
-- Matrix multiplication
case a * b of
Ok result -> Matrix.toRows result
Err msg -> [[]]
-- Element-wise multiply (Hadamard product)
Matrix.toRows (a .* b) -- [[5.0, 12.0], [21.0, 32.0]]
-- Scale: multiply every element by a scalar
Matrix.scale 2.0 a |> Matrix.toRows -- [[2.0, 4.0], [6.0, 8.0]]
-- Element-wise map
Matrix.map (\x -> x * x) a |> Matrix.toRows -- [[1.0, 4.0], [9.0, 16.0]]
Try itDecompositions
Decompositions operate on Matrix Float only. They return Result values so dimension and singularity errors are handled in keel code:
import Matrix
let m = Matrix.fromRows [[4.0, 3.0], [6.0, 3.0]]
-- Inverse
case Matrix.inv m of
Ok mi -> Matrix.toRows mi
Err e -> [[]]
-- Determinant
case Matrix.det m of
Ok d -> d
Err e -> 0.0
-- Frobenius norm (infallible)
Matrix.norm m -- sqrt(sum of squared elements)
-- SVD: (U, singular values, V^T)
let (u, s, vt) = Matrix.svd m
-- QR decomposition: (Q, R)
let (q, r) = Matrix.qr m
-- Cholesky: requires a positive-definite matrix
let spd = Matrix.fromRows [[4.0, 2.0], [2.0, 3.0]]
case Matrix.chol spd of
Ok l -> Matrix.toRows l
Err e -> [[]]
Try itNullable Matrices
Use Matrix (Maybe Float) for sparse data or when some values may be absent:
import Matrix
let sparse = Matrix.fromRowsMaybe
[ [Just 1.0, Nothing, Just 3.0]
, [Nothing, Just 5.0, Nothing]
]
-- Fill missing values with a default, then check shape
let filled = Matrix.fromRowsMaybeWith 0.0
[ [Just 1.0, Nothing, Just 3.0]
, [Nothing, Just 5.0, Nothing]
]
Matrix.shape filled -- (2, 3)
Try itDataFrame Interop
Matrices and DataFrames can be converted in both directions:
import Matrix
import DataFrame
let df = DataFrame.fromRecords [{ x = 1.0, y = 2.0 }, { x = 3.0, y = 4.0 }]
-- DataFrame columns → Matrix Float (fails on nulls)
case Matrix.fromDataFrame df of
Ok m -> Matrix.shape m -- (2, 2)
Err e -> (0, 0)
Try itSolving Linear Systems
Matrix.solve a b solves Ax = b for x (uses LU decomposition):
import Matrix
-- Solve: 2x = 6, 4y = 8
let a = Matrix.fromRows [[2.0, 0.0], [0.0, 4.0]]
let b = Matrix.fromList [6.0, 8.0]
case Matrix.solve a b of
Ok x -> Matrix.toList x -- Ok [3.0, 2.0]
Err e -> Err e
Try itFloat-only operations
Several operations require Matrix Float because they rely on floating-point arithmetic internally. Passing any other element type is a compile error — the type checker enforces this before the program runs.
| Operation | Why Float only |
|---|---|
Matrix.inv | LU decomposition requires floating-point pivoting |
Matrix.det | Computed via LU; result is always a real number |
Matrix.solve | LU-based linear solve; produces a float solution vector |
Matrix.svd | Singular value decomposition — iterative float algorithm |
Matrix.svdTruncated | Truncated SVD variant of the same |
Matrix.qr | QR factorisation via Householder reflections |
Matrix.chol | Cholesky factorisation; requires positive-definite float input |
Matrix.norm | Frobenius norm — sum of squares, then square root |
Matrix.trace | Sum of diagonal; result type is Float |
Matrix.rank | Rank via SVD — requires float computation |
Matrix.toDataFrame | Produces Float columns; only meaningful for float data |
Matrix.fromDataFrame | Always produces Matrix Float |
The ./ operator also always produces Matrix Float, even when both inputs are Matrix Int — division cannot stay exact for integers.
To use any Float-only operation on integer data, convert first with Matrix.toFloat:
import Matrix
let ints = Matrix.fromRowsInt [[1, 2], [3, 4]]
let floats = Matrix.toFloat ints -- Matrix Float
Matrix.norm floats -- ok: Frobenius norm
Matrix.inv floats -- ok: matrix inverse
Try itMatrix Decimal has no conversion path to Matrix Float — Decimal is arbitrary-precision and cannot be silently narrowed. If you need decompositions on decimal data, round or truncate to Float explicitly before constructing the matrix.
Type Safety
The Keel compiler enforces element-type restrictions at compile time:
Matrix Stringis a compile error — only numeric element types are permittedMatrix (Maybe String)is a compile error — Maybe must wrap a numeric typeMatrix Int |> Matrix.normis a compile error —normrequiresMatrix Float; useMatrix.toFloatfirstMatrix Float == Matrix Floatis a compile error — useMatrix.toRowsand compare element-wise with a tolerance