But hey, at least we get to use some fancy math terms like eigenvectors and singular values while coding.
Before anything else: what is linear algebra? Well, it’s the study of vector spaces and their transformations using matrices (which are just arrays with fancy names). And in Haskell, we can use libraries like `linear` or `vector-space` to perform these operations. But let’s not get too technical instead, let’s focus on how to actually write some code!
Let’s say you have a matrix (which is just a list of lists) and want to find its determinant. Here’s an example using `linear`:
-- Importing the necessary library for matrix operations
import Linear.Matrix
-- Defining a function to convert a list of lists to a matrix
fromLists :: [[Double]] -> Matrix Double
fromLists = fromRows . map fromIntegral
-- Defining a function to calculate the determinant of a matrix
determinant :: Matrix a => Matrix a -> Maybe (a, Int)
-- Checking if the matrix is a 1x1 matrix, in which case the determinant is just the single element
determinant m | n == 1 = Just $ product (map head (transpose (take n (rows m))))
-- For larger matrices, we use the following algorithm
| otherwise = do
-- Calculating the determinant using the determinant' function
let det' = determinant' m
-- Calculating the sign of the determinant using the (-1)^n-1 formula
sign' = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head m)]]
-- Returning the determinant and the size of the matrix as a tuple
return $ (det', length (head m))
where n = rows m -- Defining n as the number of rows in the matrix
-- Defining a helper function to calculate the determinant of a submatrix
determinant' :: Matrix a => Matrix a -> Maybe (a, Int)
determinant' m = do
-- Calculating the submatrix using the matrixSubstitution function
let submatrix = matrixSubstitution m 0
-- Calculating the determinant of the submatrix using the determinant function
det = determinant submatrix
-- Calculating the sign of the determinant using the (-1)^n-1 formula
sign = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head m)]]
-- Returning the determinant and the size of the submatrix as a tuple
return $ (det, length (head submatrix))
Wow, that’s a lot of code! But let me break it down for you. First, we define a helper function called `fromLists` which converts our input list-of-lists into a matrix using the `linear` library. Then, we have two functions: `determinant` and `determinant’`. The former is just a wrapper around the latter, while the latter does most of the heavy lifting.
The `determinant’` function uses recursion to calculate the determinant using Laplace expansion along the first row (which is why we need to keep track of the sign). We also use another helper function called `matrixSubstitution` which removes a given row and column from our matrix.
Now, tensors! Tensors are just fancy arrays with multiple dimensions think of them as matrices that can have more than two dimensions (which is why they’re sometimes called n-dimensional arrays). In Haskell, we can use the `vector-space` library to work with tensors.
Let’s say you want to calculate the dot product between two 3D vectors using `vector-space`. Here’s an example:
-- Import the VectorSpace library to work with tensors
import VectorSpace
-- Define a function to convert a list of Doubles into a Vector
fromList :: [Double] -> V Double
-- Use the fromLists function from the VectorSpace library to convert the list into a Vector
fromList = fromLists . map fromIntegral
-- Define a function to calculate the dot product between two Vectors
dotProduct :: (Vector v, Num a) => V v a -> V v a -> Maybe (a, Int)
-- Check if the Vectors have a dimension of 1
dotProduct x y | n == 1 = Just $ product (zipWith (*) (take n x) (take n y))
-- If the Vectors have a dimension greater than 1, use the dotProduct' function
| otherwise = do
let dot' = dotProduct' x y
-- Calculate the sign of the dot product using the (-1)^(n-1) formula
sign' = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head x)]]
-- Return the dot product and the length of the first Vector
return $ (dot', length (head x))
where n = dim x
-- Define a helper function to calculate the dot product recursively
dotProduct' :: forall v. (Vector v, Num a) => V v a -> V v a -> Maybe (a, Int)
dotProduct' x y = do
-- Use the vectorSubstitution function to get the sub-vectors of x and y
let subVectors = vectorSubstitution x 0
-- Calculate the dot product of the sub-vectors
dot = dotProduct' subVectors (vectorSubstitution y 0)
-- Calculate the sign of the dot product using the (-1)^(n-1) formula
sign = (-1)^(n-1)*product [i `mod` 2 | i <- [0..length (head x)]]
-- Return the dot product and the length of the sub-vectors
return $ (dot, length (head subVectors))
Again, we have a helper function called `fromList` which converts our input list into a vector using the `vector-space` library. Then, we define two functions: `dotProduct` and `dotProduct’`. The former is just a wrapper around the latter, while the latter does most of the heavy lifting.
The `dotProduct’` function uses recursion to calculate the dot product between our vectors using Laplace expansion along the first dimension (which is why we need to keep track of the sign). We also use another helper function called `vectorSubstitution` which removes a given vector from our space.
And that’s it! You now know how to perform linear algebra and tensor operations in Haskell using fancy math terms like determinants and dot products. Who needs sliced bread when you have this kind of excitement?