2

As an exercise, I'm trying to create a Vector typeclass as an exercise:

class Vector v where
  vplus :: v -> v -> v
  vnegate :: v -> v

type V3 a = (a,a,a)

instance (Num a) => Vector (V3 a) where
  (a,b,c) `vplus` (d,e,f) = (a+d, b+e, c+f)
  vnegate (a,b,c) = ((-a), (-b), (-c))

I want to add a dot function on the typeclass. On the V3 example above, I'd implement it as follows:

dot :: (Num a) => V3 a -> V3 a -> a
(a,b,c) `dot` (d,e,f) = a*d + b*e + c*f

However, it appears I don't have access to the type parameter a from within Vector, so I can't have dot operate over Vector the way I want. How would I access the a type parameter?

2 Answers 2

3

Another solution, which requires no extensions, is to use a higher-kinded class. Thus:

class Vector v where
    vplus :: Num a => v a -> v a -> v a
    vnegate :: Num a => v a -> v a

Then it's easy to add a dot-product method:

    dot :: Num a => v a -> v a -> a

The instance method implementations won't have to change, though the instance declaration itself would have to change:

instance Vector V3 where
    -- method implementations are the same as before
Sign up to request clarification or add additional context in comments.

Comments

1

You want to use TypeFamilies for this to create an associated type:

{-# LANGUAGE TypeFamilies, TypeSynonymInstances, FlexibleInstances #-}

class Vector v where
    -- Declares a family of types called Item, parametrized on the
    -- instance v of Vector, and the kind of Item v must be *,
    -- meaning that it must be a type, not a type constructor
    -- (e.g. Maybe Int :: * vs Maybe :: * -> *)
    type family Item v :: *
    dot :: v -> v -> Item v
    ...

instance (Num a) => Vector (V3 a) where
    type Item (V3 a) = a
    dot (a, b, c) (d, e, f) = a*d + b*e + c*f
    ...

Then you can do

> dot (1, 2, 3) ((4, 5, 6) :: V3 Int)
32

Although I would recommend against using a type synonym instance, you'd be better off using a data type:

data V3 a = V3 a a a deriving (Eq, Show)

instance Functor V3 where
    fmap f (V3 a b c) = V3 (f a) (f b) (f c)

instance (Num a) => Vector (V3 a) where
    type Item (V3 a) = a
    (V3 a b c) `vplus` (V3 d e f) = V3 (a + d) (b + e) (c + f)
    vnegate v = fmap negate v
    dot (V3 a b c) (V3 d e f) = a*d + b*e + c*f

This helps out the type checker a lot, in particular it means you wouldn't need the explicit type signature above. It also means your inferred types won't be (a, a, a) but V3 a (like when you see [Char] instead of String), which is easier to follow. It's not crucial, but helpful.

In case you're wondering, this is how GHC.Exts.IsList (for use with the new OverloadedLists extension) does it:

class IsList l where
    type family GHC.Exts.Item l :: *
    fromList :: [GHC.Exts.Item l] -> l
    fromListN :: Int -> [GHC.Exts.Item l] -> l
    toList :: l -> [GHC.Exts.Item l]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.