Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typeclass instance with functional dependencies doesn't work

Playing around with type-classes I came up with the seemingly innocent

class Pair p a | p -> a where
  one :: p -> a
  two :: p -> a

This seems to work fine, e.g.

instance Pair [a] a where
  one [x,_] = x
  two [_,y] = y 

However I run in trouble for tuples. Even though the following definition compiles...

instance Pair (a,a) a where
  one p = fst p 
  two p = snd p

... I can't use it as I expected:

main = print $ two (3, 4)

No instance for (Pair (t, t1) a)
  arising from a use of `two' at src\Main.hs:593:15-23
Possible fix: add an instance declaration for (Pair (t, t1) a)
In the second argument of `($)', namely `two (3, 4)'
In the expression: print $ two (3, 4)
In the definition of `main': main = print $ two (3, 4)

Is there a way to define the instance correctly? Or do I have to resort to a newtype wrapper?

like image 495
Landei Avatar asked Dec 16 '11 15:12

Landei


2 Answers

Your instance works just fine, actually. Observe:

main = print $ two (3 :: Int, 4 :: Int)

This works as expected. So why doesn't it work without the type annotation, then? Well, consider the tuple's type: (3, 4) :: (Num t, Num t1) => (t, t1). Because numeric literals are polymorphic, nothing requires them to be the same type. The instance is defined for (a, a), but the existence of that instance won't tell GHC to unify the types (for a variety of good reasons). Unless GHC can deduce by other means that the two types are the same, it won't choose the instance you want, even if the two types could be made equal.

To solve your problem, you could just add type annotations, as I did above. If the arguments are coming from elsewhere it's usually unnecessary because they'll already be known to be the same type, but it gets clumsy quickly if you want to use numeric literals.

An alternative solution is to note that, because of how instance selection works, having an instance for (a, a) means that you can't write an instance like (a, b) as well even if you wanted to. So we can cheat a bit, to force the unification using the type class, like this:

instance (a ~ b) => Pair (a,b) a where

That needs the TypeFamilies extension for the ~ context, I think. What this does is allow the instance to match on any tuple at first, because instance selection ignores the context. After choosing the instance, however, the a ~ b context asserts type equality, which will produce an error if they're different but--more importantly here--will unify the type variables if possible. Using this, your definition of main works as is, without annotations.

like image 194
C. A. McCann Avatar answered Nov 16 '22 02:11

C. A. McCann


The problem is that a literal number has a polymorphic type. It is not obvious to the typechecker that both literals should have the same type (Int). If you use something that is not polymorphic for your tuples, your code should work. Consider these examples:

*Main> two (3,4)

<interactive>:1:1:
    No instance for (Pair (t0, t1) a0)
      arising from a use of `two'
    Possible fix: add an instance declaration for (Pair (t0, t1) a0)
    In the expression: two (3, 4)
    In an equation for `it': it = two (3, 4)
*Main> let f = id :: Int -> Int -- Force a monomorphic type
*Main> two (f 3,f 4)
4
*Main> two ('a','b')
'b'
*Main> two ("foo","bar")
"bar"
*Main> two (('a':),('b':)) "cde"
"bcde"
*Main>
like image 29
fuz Avatar answered Nov 16 '22 01:11

fuz