Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get rid of this ambiguity?

I am pretty sure this has been asked before, however I was unable to find the right answer:

I tried to eliminate the ambiguity in the following exemplary code snippet:

{-# LANGUAGE MultiParamTypeClasses #-}

class FooBar a b where
    foo :: a -> a
    foo = id
    bar :: a -> a
    bar = foo        -- ERROR AT THIS LINE

I get an error message like so:

Ambiguous type variable `b0' in the constraint:
      (FooBar a b0) arising from a use of `foo'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: foo
    In an equation for `bar': bar = foo

which is understandable. Note however, that I'm actually unable to follow the compiler's advice and fix the type variable in question: foo doesn't contain b type variable in its type signature. Also this doesn't work:

    bar = (foo :: FooBar a b => a -> a) -- ERROR, the same error message

Not even with -XScopedTypeVariables enabled.

How to tell GHC which FooBar to use? Is it even possible?

EDIT

Following some answers: yes, I've heard about both functional dependencies and associated types.

As to functional dependencies - I am looking for a way to explicitly inform GHC which instance to use (as opposed to asking it to derive the correct type all by itself).

As to both - in the real world example that this one comes from, I actually need both of the parameters a and b to be independent.

Again: I know this example is extremely silly, I know b is not used in the body of the type class, which makes no sense. This is an extreme simplification of a real world example, that actually does make sense. The real use-case involves "substitutability" of types a using types b and then "unifiability" of types a and b using types c and unfortunately is much longer.

EDIT(2)

Okay sorry. I think you convinced me after all that I have to refactor the code in order not to have any ill-defined functions (i.e. ones that don't contain all the independent types in their type signatures). Talking to you really helped me think this thing though. Appreciated.

like image 958
julx Avatar asked Dec 07 '11 13:12

julx


2 Answers

Your class is ill-defined even if you remove bar completely from the class. Let's just assume you have the following class:

class FooBar a b
  where
    foo :: a -> a
    foo = id

Suppose now you would like to use foo outside the definition of this class.

e = .... foo x .....

GHC will complain with a similar error message than yours, that it can't find a type for b. Simply because your code doesn't supply such a type. Your type class declaration says every pair of types a and b might get an instance. So, even when a is fixed in a call of foo there are still arbitrary many possible instances to select from.

A functional dependency a -> b would solve that problem, because it states: Given a type a there may be only one instance of Foobar a b. An associated type avoids the problem by not using multi-parameter type classes.

regarding the OP's EDIT:

If you insist on having two type parameters. Then every method's signature of the type class has to contain every type variable of the class. There is no way around it.

like image 177
jmg Avatar answered Sep 27 '22 16:09

jmg


If you introduce a functional dependency, class FooBar a b | a -> b where, that would settle it, but without that (or associated types), I think it is impossible to resolve the instance to use.

Oh, one way, but kind of ugly: introduce a dummy parameter of type b

class FooBar a b where
    foo' :: b -> a -> a
    foo' _ = id
    bar' :: b -> a -> a
    bar' b = foo b
like image 23
Daniel Fischer Avatar answered Sep 27 '22 18:09

Daniel Fischer