Reputation: 2768
I've got a typeclass that's defined like this:
class (Eq a, Show a, Typeable a) => Condition v a where
(and some methods)
I wanted to write a function that takes two Conditions that both have the same 'v'.
analyze :: (Condition v a, Condition v b) => a -> b -> Bool
(Return type made Bool for simplicity)
But when I try to compile this, I get
Could not deduce (Condition v0 b)
arising from the ambiguity check for ‘analyze’
from the context (Condition v a, Condition v b)
So it's clearing not deducing what I wanted to say, and instead has introduced another type variable v0.
Could you explain what's wrong with the formulation of the type signature as I've written it and how to fix it? Do RankNTypes address the problem?
Upvotes: 4
Views: 115
Reputation: 7138
Yet another approach is to use type families instead of functional dependencies (which also eliminates the need for multiparameter typeclasses):
class (Eq a, Show a, Typeable a) => Condition a where
type V a :: *
analyze :: (Condition a, Condition b, V a ~ V b) => a -> b -> Bool
Answering the comment from OP: Type families "might also be regarded as an alternative to functional dependencies, but provide a more functional style of type-level programming than the relational style of functional dependencies". They are later addition to GHC so many older libraries still use functional dependencies. There are some differences between TF and FD (e.g. the way overlapping instances are handled) but I think it is generally accepted that for newer code TF is preferable approach. Here is a report outlining the experience of converting FD to TF
Upvotes: 1
Reputation: 876
A functional dependency, as described in another answer, could solve the problem. I'd like to point out another option: using a type proxy.
class (Eq a, Show a, Typeable a) => Condition v a where
-- Introduce v into the type of analyze, without demanding that a
-- value of type v be provided; instead, just give some value which
-- indicates v as a type parameter (like a Proxy, for instance).
analyze :: (Condition v a, Condition v b) => proxy v -> a -> b -> Bool
instance Condition Bool Bool where
...
instance Condition Int Bool where
...
-- Use a proxy to pick out the type of v.
exampleInt = analyze (Proxy :: Proxy Int) True False
exampleBool = analyze (Proxy :: Proxy Bool) True False
Since you're using Typeable
, you probably have Proxy
in scope already, as Data.Typeable
exports it.
Using functional dependencies rules out the above example: you cannot have two instances of Condition
for the same a
. Maybe that's fine, but if not, consider using a type proxy.
Upvotes: 1
Reputation: 14588
Condition v a
requires both v
and a
to be resolved. Since v
is not mentioned in the right hand side of the =>
in analyze
, the compiler has no way to pick the correct v
. It is the same as having foo :: Show a => ()
or foo :: Condition a x => x -> Bool
. How do you select an a
? I don't know what the interpretation of Condition
is, but it may be the case that v
should be determined by a
- if this is the case, then you can write
class Condition v a | a -> v
which is called a functional dependency.
Upvotes: 3