Suppose I define a multi-parameter type class:
{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts, FlexibleInstances #-}
class Table a b c where
decrement :: a -> a
evalutate :: a -> b -> c
Then I define a function that uses decrement
, for simplicity:
d = decrement
When I try to load this in ghci
(version 8.6.3):
• Could not deduce (Table a b0 c0)
arising from a use of ‘decrement’
from the context: Table a b c
bound by the type signature for:
d :: forall a b c. Table a b c => a -> a
at Thing.hs:13:1-28
The type variables ‘b0’, ‘c0’ are ambiguous
Relevant bindings include d :: a -> a (bound at Thing.hs:14:1)
These potential instance exist:
instance Table (DummyTable a b) a b
This is confusing to me because the type of d
is exactly the type of decrement
, which is denoted in the class declaration.
I thought of the following workaround:
data Table a b = Table (a -> b) ((Table a b) -> (Table a b))
But this seems notationally inconvenient, and I also just wanted to know why I was getting this error message in the first place.
The problem is that, since decrement
only requires the a
type, there is no way to figure out which types b
and c
should be, even at the point where the function is called (thus solving the polymorphism into a specific type) - therefore, GHC would be unable to decide which instance to use.
For example: let's suppose you have two instances of Table: Table Int String Bool
, and Table Int Bool Float
; you call your function d
in a context where it is supposed to map an Int to another Int - problem is, that matches both instances! (a
is Int for both).
Notice how, if you make your function equal to evalutate
:
d = evalutate
then the compiler accepts it. This is because, since evalutate
depends on the three type parameters a, b, and c, the context at the call site would allow for non-ambiguous instance resolution - just check which are the types for a, b, and c at the place where it is called.
This is, of course, not usually a problem for single-parameter type classes - only one type to resolve; it is when we deal with multiple parameters that things get complicated...
One common solution is to use functional dependencies - make b
and c
depend on a
:
class Table a b c | a -> b c where
decrement :: a -> a
evalutate :: a -> b -> c
This tells the compiler that, for every instance of Table for a given type a
, there will be one, and only one, instance (b
and c
will be uniquely determined by a
); so it will know that there won't be any ambiguities and accept your d = decrement
happily.
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