Reputation: 3647
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?
Upvotes: 2
Views: 322
Reputation: 153342
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
Upvotes: 3
Reputation: 54078
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]
Upvotes: 1