Home > OS >  how do you do recursion with type classes and data type in Haskell?
how do you do recursion with type classes and data type in Haskell?

Time:12-07

I am working on a type class for fraction, vector, and matrix arithmetic (ie add, sub, mul) but can't quite get the vector instance because I don't know how to work with the recursive nature of the function.

here's the code:

class MathObject a where
    add :: a -> a -> a
    sub :: a -> a -> a
    mul :: a -> a -> a


type Vector = [Int]


data Vec = Vec Vector deriving (Show, Eq)


instance MathObject Vec where 
    add (Vec v1) (Vec v2) = addVecs (Vec v1) (Vec v2)
    sub (Vec v1) (Vec v2) = subVecs (Vec v1) (Vec v2)
    mul (Vec v1) (Vec v2) = mulVecs (Vec v1) (Vec v2)

addVecs :: Vec -> Vec -> [Int]
addVecs (Vec []) (Vec vec2)= (Vec [])
addVecs (Vec vec) (Vec []) = (Vec [])
addVecs (Vec (x:xs)) (Vec (y:ys)) = e1 e2:rest where
  e1 = x
  e2 = y
  rest = addVecs (Vec xs) (Vec ys)

subVecs :: Vec -> Vec -> [Int]
subVecs (Vec []) (Vec v2) = (Vec [])
subVecs (Vec (x:xs)) (Vec (y:ys)) = x-y:rest where
  rest = subVecs (Vec xs) (Vec ys)

mulVecs :: Vec -> Vec -> [Vec]
mulVecs (Vec []) (Vec vec2) = (Vec [])
mulVecs (Vec (x:xs)) (Vec (y:ys)) = e1 * e2:rest where
  e1 = x
  e2 = y
  rest = mulVecs (Vec xs) (Vec ys)

I made the fraction instance and it works so I have some basic understanding of how type classes work but I just don't know how to deal with a recursive type.

CodePudding user response:

I would say, first, you have confused yourself with too many different things named something that sounds like "vector". Why does the Vec type have a constructor named Vec and fields of type Vector? What's the difference really between a Vec and a Vector? If Vector is an alias for [Int], why do you use ordinary [Int] to represent vectors sometimes? You don't include the compiler errors you get, but I can see clearly that at least some of them will arise due to type mismatches between these names.

So the first thing to do is simplify it: just have one Vector type, which is a newtype (or data if you haven't gotten to newtypes yet) wrapper around [Int]:

newtype Vector = Vector [Int]

Now you always know whether you're working with a Vector or an [Int], and can convert between the two using the Vector constructor.

My next observation would be that addVecs and friends clearly have the wrong type: why do they take two Vector inputs and return an [Int]? They should stick with one type, preferably Vector. This is the cause of one of your type errors: add needs to be of type a -> a -> a, but you've defined add to be addVecs, which has type Vec -> Vec -> [Int] - that doesn't fit! So let's instead write

addVecs :: Vector -> Vector -> Vector
-- ...

Now, the implementation is right given the poor type you chose for it. But to work for the better type, we'd have to add a Vector wrapper around a b before consing it to the result. I won't show that implementation, though, because there's a much better one available. Instead of doing the recursion yourself, use one of Haskell's many flexible tools for working with lists: zipWith.

addVecs (Vector v1) (Vector v2) = Vector $ zipWith ( ) v1 v2

This does exactly the same thing as your implementation, but all the tedious wrapping and unwrapping is done only once, and recursive list processing is completely abstracted away by zipWith.

You could do the same thing for subVecs and mulVecs, but you would quickly notice there's some duplication here. It would be better to extract that out into a function, so that those three functions can all share the common code:

pointwise :: (Int -> Int -> Int) -> (Vector -> Vector -> Vector)
pointwise f (Vector v1) (Vector v2) = Vector $ zipWith f v1 v2

addVecs = pointwise ( )
subVecs = pointwise (-)
mulVecs = pointwise (*)

At this point you don't even really need definitions for addVecs and friends - you could easily inline them into the MathObject instance:

instance MathObject Vector where
  add = pointwise ( )
  sub = pointwise (-)
  mul = pointwise (*)
  • Related