6
votes

Alright, I'm trying to wrap my head around typeclasses, and so I'm trying to define a typeclass for geometric vector operations. I managed to get it working for component-wise +,-,*,/; but I'm struggling with the dot product.

class GeomVector a where
  (>+) :: a -> a -> a
  (>-) :: a -> a -> a
  (>*) :: a -> a -> a
  (>/) :: a -> a -> a

  (>.) :: a -> a -> Double

data Vector a = Vec [a]
              deriving Show

instance (Fractional a) => GeomVector (Vector a) where
  (>+) (Vec u) (Vec v) = Vec $ zipWith (+) u v
  (>-) (Vec u) (Vec v) = Vec $ zipWith (-) u v
  (>*) (Vec u) (Vec v) = Vec $ zipWith (*) u v
  (>/) (Vec u) (Vec v) = Vec $ zipWith (/) u v

  (>.) (Vec u) (Vec v) = sum $ u >* v

Obviously my instance definition for (>.) won't work because the result is of type Fractional a, not Double.

But I don't know how to get this behavior from the declaration in the class.

What I'd like to do is:

class GeomVector [a] where
  (>.) :: [a] -> [a] -> a

But this is invalid because [a] is a type and not a type variable.

I wish I could explain this a little better, but I honestly don't understand enough to do so. Hopefully the code will make it a little more obvious what I'm struggling with.

1
I think you need another type variable to denote the type of the scalars, i.e. class GeomVector a s where ... (>.) :: a -> a -> s. - ErikR
Your class declaration is flawed not only because of (>.) result type. You attemtp to produce a dot product of u and v which are lists, not instances of your class. - Dmitry Dzhus
@Lambdageek Why synonyms? Isn't it just an associated type which is needed here? (data Scalar a)? - Dmitry Dzhus
@DmitryDzhus Scalar a should be some existing type (for example, for instance GeomVector [a] it should be a), not a brand new data type. - Lambdageek

1 Answers

5
votes

Here's one option that could work:

class GeomVector v where
  (>+) :: Num a=> v a -> v a -> v a
  (>-) :: Num a=> v a -> v a -> v a
  (>*) :: Num a=> v a -> v a -> v a
  (>/) :: Fractional a=> v a -> v a -> v a
  (>.) :: Num a=> v a -> v a -> a

data Vector a = Vec { vecList :: [a] }
              deriving Show

instance GeomVector Vector where
  (>+) (Vec u) (Vec v) = Vec $ zipWith (+) u v
  (>-) (Vec u) (Vec v) = Vec $ zipWith (-) u v
  (>*) (Vec u) (Vec v) = Vec $ zipWith (*) u v
  (>/) (Vec u) (Vec v) = Vec $ zipWith (/) u v

  (>.) u v = sum $ vecList (u >* v)

So all your instances of GeomVector will have a kind * -> * like the Monad class. And the types of the methods are not unnecessarily restricted to Fractional types just because you divide somewhere in there.

You might also consider making your class as small as possible (make >. a polymorphic function outside of the class) and whether what you really want is a type class to begin with. But all that depends on what you're designing, and I don't want to presume that I know better than you about that!