Reputation: 4219
I have two relatively simple classes, MSet
:
{-# Langage MultiParamTypeClasses #-}
-- A generalization of G set to Magmas
class MSet a b where
(+>>) :: a -> b -> a
instance MSet Integer Integer where
(+>>) = (+)
-- (+>>) constrained to a Magma
(<<+>>) ::
( MSet a a
)
=> a -> a -> a
(<<+>>) = (+>>)
When I boot into ghci and try and test these I have an issue:
*Main> 1 <<+>> 2
3
*Main> 1 +>> 2
<interactive>:31:1: error:
• Could not deduce (MSet a b0)
from the context: (MSet a b, Num a, Num b)
bound by the inferred type for ‘it’:
forall a b. (MSet a b, Num a, Num b) => a
at <interactive>:31:1-7
The type variable ‘b0’ is ambiguous
• In the ambiguity check for the inferred type for ‘it’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the inferred type
it :: forall a b. (MSet a b, Num a, Num b) => a
While (+>>)
works when it is constrained to a Magma
but is ambiguous when it is not.
Now I can do:
*Main> :set -XFlexibleContexts
*Main> 1 +>> (2 :: Integer)
3
But I don't understand what is going on here or why this annotation helps. I don't really get how the type checker disambiguates (<<+>>)
. If I add another instance, say Int
, it continues to function even though it seems to be it should be ambiguous whether 1
is an Int
or an Integer
.
Why does one of these error and why do the other two not?
Upvotes: 4
Views: 207
Reputation: 1852
Essentially - you are asking GHC to solve "what is the type of 1
and 2
in the expression 1 +>> 2
, and your typeclasses mean that the answer is ambiguous.
<<+>>
What is the type of 1 <<+>> 2
? Why, (Num a, MSet a a) => a
of course, because GHC has to be able to turn the literals into values (Num
) and then +>>
them (MSet
), and the type signature of <<+>>
says that both literals will have the same type.
What happens when you ask GHCi to print the value of 1 <<+>> 2
? It tries to default a
to Integer
, which succeeds because Num Integer
and MSet Integer Integer
. It then evaluates the type-defaulted expression.
This is the reason the Int
instance doesn't introduce ambiguity - GHCi isn't trying to infer the specific instance to use, it instead infers the type and then defaults the variables, leaving only the instance check.
+>>
What is the type of 1 +>> 2
? Well... (Num a, Num b, MSet a b) => a
, it seems like. Clearly you need MSet
, but there's no longer an assurance that a
and b
unify. More unfortunately, b
does not appear in the type of the term - which is the source of the type ambiguity. The type system doesn't know which choice of b
to use.
What happens when you ask GHCi to print the value of 1 +>> 2
? It first infers the type of the term, and gets the above type - and now it hits a type inference error before it can try to default a
to Integer
.
Adding type information will prevent the error
> 1 +>> (2 :: Integer) -- fine
because these changes eliminate the ambiguity of b
. GHC doesn't need to infer b
, so it doesn't get raise an inference error.
Weirdly, and I don't fully understand the reason for it, adding the annotation for a
also seems to prevent the error
> (1 :: Integer) +>> 2 -- fine
> (1 +>> 2) :: Integer -- fine
though I suspect that's another GHCi-specific trick that's defaulting b
to Integer
in (1 +>>) :: (Num b, MSet Integer b) => b -> Integer
. Don't quote me on that, though.
It is possible to eliminate the type ambiguity using FunctionalDependencies
class MSet a b | a -> b where
...
though that doesn't seem like it fits your use case. This solves the inference problem because in (Num a, Num b, MSet a b) => a
, knowing a
will be enough to deduce b
from the fundep in MSet
. Later, when GHCi defaults a
to Integer
, it can just look up the type of b
.
Upvotes: 1