I was playing around with the FunctionalDependencies-Extension of Haskell, along with MultiParamTypeClasses. I defined the following:
class Add a b c | a b -> c where
(~+) :: a -> b -> c
(~-) :: a -> b -> c
neg :: a -> a
zero :: a
which works fine (I’ve tried with instances for Int and Double with the ultimate goal of being able to add Int and Doubles without explicit conversion).
When I try to define default implementations for neg or (~-) like so:
class Add ...
...
neg n = zero ~- n
GHCi (7.0.4) tells me the following:
Ambiguous type variables `a0', `b0', `c0' in the constraint:
(Add a0 b0 c0) arising from a use of `zero'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `(~-)', namely `zero'
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n
Ambiguous type variable `a0' in the constraint:
(Add a0 a a) arising from a use of `~-'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n
I think I do understand the problem here. GHC does not know which zero to use, since it could be any zero yielding anything which in turn is fed into a ~- which we only know of, that it has an a in it’s right argument and yields an a.
So how can I specify that it should be the zero from the very same instance, i.e. how can I express something like:
neg n = (zero :: Add a b c) ~- n
I think the a, b and c here are not the a b c form the surrounding class, but any a b and c, so how can I express a type which is a reference to the local type variables?
Pull
negandzeroout into a superclass that only uses the one type:The point is that your way,
zero :: Intcould be thezerofromAdd Int Int Int, or thezerofromAdd Int Double Double, and there is no way to disambiguate between the two, regardless of whether you’re referring to it from inside a default implementation or an instance declaration or normal code.(You may object that the
zerofromAdd Int Int Intand thezerofromAdd Int Double Doublewill have the same value, but the compiler can’t know that someone isn’t going to defineAdd Int Char Boolin a different module and givezeroa different value there.)By splitting the typeclass into two, we remove the ambiguity.