4
votes

So here is a nested list [[1, 2], [3, 4]]

I want to wrap it in a type called Matrix, and make it an instance of the classes Eq, Num, and Show

I have already created (add, sub, mul) operations for nested lists (matrices). How do I overload (+ - *) operators so that + maps to add, - maps to sub, and * maps to mul? So I can do this

> ma = Matrix [[1, 2], [3, 4]]
> mb = Matrix [[5, 6], [7, 8]]
> ma + mb
> ma - mb
> ma * mb

Thanks

EDIT

this is my attempt so far

> add = zipWith (zipWith (+))
> sub = zipWith (zipWith (-))
> data Matrix a = Matrix [[a]] deriving (Eq, Show)
> instance Num (Matrix a)
>   where
>   (+) x y = Matrix $ add x y
>   (-) x y = Matrix $ sub x y

This is what I get from ghci

 Couldn't match expected type `[[c0]]' with actual type `Matrix a'
    In the first argument of `sub', namely `x'
    In the second argument of `($)', namely `sub x y'
    In the expression: Matrix $ sub x y

EDIT #2 The last thing I need to figure out right now is

how to I print

1 2
3 4

Instead of Matrix [[1,2],[3,4]]

2
@Mikhail Blame my grammar error on too much Haskell Kool-Aidnobody
Replace (+) x y with (+) (Matrix x) (Matrix y). The error message tells you that sub expects a [[a]], but you're giving it Matrix a.Mikhail Glushenkov
Added a Show instance that you requested.Mikhail Glushenkov

2 Answers

7
votes

Are you having a problem with defining a Num instance for your type? Try this code:

data Matrix a = Matrix [[a]]
              deriving (Eq)

plus_mat :: Num a => [[a]] -> [[a]] -> [[a]]
plus_mat = zipWith (zipWith (+))

instance Num a => Num (Matrix a)
  where
    (Matrix a) + (Matrix b) = Matrix $ plus_mat a b
    (-)                     = undefined
    (*)                     = undefined
    negate                  = undefined
    abs                     = undefined
    signum                  = undefined
    fromInteger             = undefined

Testing:

*Main> Matrix [[1,2],[3,4]] + Matrix [[5,6],[7,8]]
Matrix [[6,8],[10,12]]

Definitions of the remaining class methods are left as exercise.

And here's a Show instance for Matrix:

import Data.List

instance Show a => Show (Matrix a)
  where
    show (Matrix a) = intercalate "\n" $ map (intercalate " " . map show) a

Testing:

*Main Data.List> Matrix [[1,2,3], [4,5,6]]
1 2 3
4 5 6
7
votes

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