In Idris 2, vectors and matrices can encode their dimensions directly in their types. This allows the type checker to statically guarantee that operations like addition and multiplication are only performed on matrices with compatible dimensions.
We represent a vector of a specific length using Vect n a. A matrix with m rows and n columns is defined as a vector of length m, where each element is a vector of length n.
module MatrixOps
import Data.Vect
||| A Matrix with `m` rows and `n` columns containing elements of type `a`.
public export
Matrix : Nat -> Nat -> Type -> Type
Matrix m n a = Vect m (Vect n a)
Matrix addition requires two matrices to have the exact same dimensions (). We can define this concisely using zip-wise operations (zipWith).
||| Adds two m x n matrices element-wise.
public export
addMatrix : Num a => Matrix m n a -> Matrix m n a -> Matrix m n a
addMatrix = zipWith (zipWith (+))
zipWith (+) adds two 1D vectors of length n element-wise.zipWith applies this vector-addition operation across all m rows.Transposing an matrix results in an matrix. In Idris, this is remarkably simple thanks to transpose provided in Data.Vect.
||| Transposes an m x n matrix into an n x m matrix.
public export
transposeMatrix : Matrix m n a -> Matrix n m a
transposeMatrix = transpose
Data.Vect.transpose leverages sequence over vectors, cleanly swapping the row and column dimensions at the type level.Matrix multiplication multiplies an matrix by a matrix to yield an matrix.
We can define a helper for the dot product of two vectors, then combine it with matrix transposition.
||| Computes the dot product of two vectors of equal length.
public export
dotProduct : Num a => Vect k a -> Vect k a -> a
dotProduct v1 v2 = sum (zipWith (*) v1 v2)
||| Multiplies an (m x k) matrix by a (k x n) matrix to get an (m x n) matrix.
public export
multMatrix : Num a => Matrix m k a -> Matrix k n a -> Matrix m n a
multMatrix mat1 mat2 =
let mat2T = transposeMatrix mat2
in map (\row => map (dotProduct row) mat2T) mat1
transposeMatrix mat2: Transposes the right matrix from to . This allows us to easily compute dot products between rows of mat1 and columns of mat2 (which are now rows in mat2T).map (\row => ...): Iterates over each row in mat1.map (dotProduct row) mat2T: Computes the dot product of the current row of mat1 with every row (column) of mat2T.Below is a complete, self-contained module demonstrating the operations in action.
module Main
import Data.Vect
Matrix : Nat -> Nat -> Type -> Type
Matrix m n a = Vect m (Vect n a)
addMatrix : Num a => Matrix m n a -> Matrix m n a -> Matrix m n a
addMatrix = zipWith (zipWith (+))
transposeMatrix : Matrix m n a -> Matrix n m a
transposeMatrix = transpose
dotProduct : Num a => Vect k a -> Vect k a -> a
dotProduct v1 v2 = sum (zipWith (*) v1 v2)
multMatrix : Num a => Matrix m k a -> Matrix k n a -> Matrix m n a
multMatrix mat1 mat2 =
let mat2T = transposeMatrix mat2
in map (\row => map (dotProduct row) mat2T) mat1
-- Example Matrices
mA : Matrix 2 3 Int
mA = [ [1, 2, 3]
, [4, 5, 6]
]
mB : Matrix 3 2 Int
mB = [ [7, 8]
, [9, 10]
, [11, 12]
]
mC : Matrix 2 3 Int
mC = [ [1, 1, 1]
, [2, 2, 2]
]
main : IO ()
main = do
putStrLn "--- Matrix Addition (mA + mC) ---"
printLn (addMatrix mA mC)
putStrLn "\n--- Transpose (transpose mA) ---"
printLn (transposeMatrix mA)
putStrLn "\n--- Matrix Multiplication (mA * mB) ---"
printLn (multMatrix mA mB)