If you inspect the type of your add
and sub
, you will see the issue.
ghci> :t add
add :: Num a => [[a]] -> [[a]] -> [[a]]
ghci> :t sub
sub :: Num a => [[a]] -> [[a]] -> [[a]]
Mikhail's suggestion was to essentially unwrap the 2D list and rewrap it in the Num instance methods. Another way to do this is to modify your add
and sub
methods to work on Matrices instead. Here I use a "lifting" approach, where I write combinators to "lift" a function from one type to another.
-- unwraps the 2d list from a matrix
unMatrix :: Matrix a -> [[a]]
unMatrix (Matrix m) = m
-- lifts a 2d list operation to be a Matrix operation
liftMatrixOp :: ([[a]] -> [[a]] -> [[a]]) -> Matrix a -> Matrix a -> Matrix a
liftMatrixOp f x y = Matrix $ f (unMatrix x) (unMatrix y)
-- lifts a regular operation to be a 2d list operation
lift2dOp :: (a -> a -> a) -> [[a]] -> [[a]] -> [[a]]
lift2dOp f = zipWith (zipWith f)
With these combinators, defining add
and sub
is simply a matter of lifting appropriately.
add, sub :: Num a => Matrix a -> Matrix a -> Matrix a
add = liftMatrixOp add2D
sub = liftMatrixOp sub2D
add2D, sub2D :: Num a => [[a]] -> [[a]] -> [[a]]
add2D = lift2dOp (+)
sub2D = lift2dOp (-)
Now that we have functions that work on Matrices, the Num instance is simple
instance (Num a) => Num (Matrix a) where
(+) = add
(-) = sub
..etc..
Of course we could have combined lift2dOp
and liftMatrixOp
into one convenience function:
-- lifts a regular operation to be a Matrix operation
liftMatrixOp' :: (a -> a -> a) -> Matrix a -> Matrix a -> Matrix a
liftMatrixOp' = liftMatrixOp . lift2dOp
instance (Num a) => Num (Matrix a) where
(+) = liftMatrixOp' (+)
(-) = liftMatrixOp' (-)
(*) = liftMatrixOp' (*)
..etc..
Now you try: define liftMatrix :: (a -> a) -> Matrix a -> Matrix a
, a lifting function for unary functions. Now use that to define negate
, abs
, and signum
. The docs suggest that abs x * signum x
should always be equivalent to x
. See if this is true for our implementation.
ghci> quickCheck (\xs -> let m = Matrix xs in abs m * signum m == m)
+++ OK, passed 100 tests.
In fact, if you write liftMatrix
with the more lenient type signature, it can be used to define a Functor
instance for Matrices.
liftMatrix :: (a -> b) -> Matrix a -> Matrix b
instance Functor (Matrix a) where
fmap = liftMatrix
Now think about how you could implement fromInteger
. Implementing this allows you to do stuff like this in ghci:
ghci> Matrix [[1,2],[3,4]] + 1
Matrix [[2,3],[4,5]]
That's how it works the way I implemented it, anyways. Remember that any numeric literal n
in Haskell code is actually transformed into fromInteger n
, which is why this works.
I think that's enough fun for now, but if you need more exercises, try getting comfortable with this Arbitrary
instance of Matrices:
instance Arbitrary a => Arbitrary (Matrix a) where
arbitrary = liftM Matrix arbitrary
(+) x y
with(+) (Matrix x) (Matrix y)
. The error message tells you thatsub
expects a[[a]]
, but you're giving itMatrix a
. – Mikhail GlushenkovShow
instance that you requested. – Mikhail Glushenkov