I am trying to understand multiparameter typeclasses but I just don't get the instance declarations. I am starting out trying to make an InnerProductSpace typeclass for a Vector type so that I can perform a dot product on two vectors. To start out I just wanted to see if I could multiply the first element of each vector. Here is my code
class InnerProductSpace a b c where
dot :: a -> b -> c
data Vector = Vector [Double]
deriving (Show)
instance InnerProductSpace Vector Vector Double where
dot (Vector a) (Vector b) = (head a * head b)
and the error after trying to use the dot function is
No instance for (InnerProductSpace Vector Vector c0)
arising from a use of `dot'
The type variable `c0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
instance InnerProductSpace Vector Vector Double
-- Defined at Vector.hs:8:10
Possible fix:
add an instance declaration for
(InnerProductSpace Vector Vector c0)
In the expression: dot a b
In an equation for `it': it = dot a b
What did I do wrong? Thanks!
The problem is that the compiler doesn't know how to choose the right instance given what it knows. If you try something like
vectorA `dot` vectorA
the compiler goes searching for the right dot
knowing that its type must be dot :: Vector -> Vector -> c0
. Unfortunately, that's just not enough information by itself—c0
could be anything and the compiler never assumes that just because it only has a single instance it must be the correct one (this is related to the open world assumption---there might be another instance out there the compiler hasn't seen yet, so it prefers to just throw an error). You could get away with it by explicitly telling the compiler what the result ought to be
vectorA `dot` vectorB :: Double
but this is tedious and likely to fail a lot with numeric types since so often those types are generic as well. For instance, which instance of Num
is used here?
(vectorA `dot` vectorB) + 3
We know it's Double
, but the compiler can't prove that.
One solution is to use Type Families as @AndrewC suggests in the comment. I actually highly recommend that. The other solution you'll see in the wild is Functional Dependencies. These are written like this
class InnerProductSpace a b c | a b -> c where
dot :: a -> b -> c
and translate to entail a promise to the compiler: "knowing a
and b
is enough information to uniquely identify c
". The compiler keeps you honest as well as it'll prevent you from writing instances that conflict on that promise.
instance InnerProductSpace Vector Vector Double where
dot (Vector a) (Vector b) = (head a * head b)
instance InnerProductSpace Vector Vector Float where
dot (Vector a) (Vector b) = (head a * head b)
---
/Users/tel/tmp/foo.hs:10:10:
Functional dependencies conflict between instance declarations:
instance InnerProductSpace Vector Vector Double
-- Defined at /Users/tel/tmp/foo.hs:10:10
instance InnerProductSpace Vector Vector Float
-- Defined at /Users/tel/tmp/foo.hs:13:10
But the promise gives the compiler exactly enough information to resolve the Double
in the previous example.
Main*> (Vector [1,2,3]) `dot` (Vector [2,3,4]) + 3.0
5
or use TypeFamilies
class (D a b ~ c) => InnerProductSpace a b c where
type D a b
dot :: a -> b -> c
or
class InnerProductSpace a b where
type D a b :: *
dot :: a -> b -> D a b
instance InnerProductSpace Vector Vector where
type D Vector Vector = Double
dot (Vector a) (Vector b) = (head a * head b)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With