Julian Birch
Julian Birch

Reputation: 2768

Ambiguity Problems with MultiParamTypeClasses

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

Answers (3)

Ed'ka
Ed'ka

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

Alexander Vieth
Alexander Vieth

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

user2407038
user2407038

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

Related Questions