Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Type inference work in presence of Functional Dependencies

Consider the code below :

{-# LANGUAGE MultiParamTypeClasses,FlexibleInstances,FunctionalDependencies,UndecidableInstances,FlexibleContexts  #-}
class Foo a c | a -> c
instance Foo Int Float 
f :: (Foo Int a) => Int -> a 
f = undefined

Now when I see the inferred type of f in ghci

> :t f
> f :: Int -> Float

Now If I add the following code

g :: Int -> Float 
g = undefined 

h :: (Foo Int a) => Int -> a 
h = g

I get the error

Could not deduce (a ~ Float)

I am not able to understand what has happened here? The restriction Foo Int a should have restricted the type of h to Int -> Float as shown in the inferred type of f.

Is it because type unification is occurring before resolving the instances ?

[Update]

An explanation given by Dan Doel on the cafe mailing list

The answer, I believe, is that the difference between the fundep implementation and type families is local constraint information. Fundeps do no local propagation.

So in your first definition, you've locally provided 'Int -> a', which is acceptable to GHC. Then it figures out externally to the function that '(Foo Int a) => Int -> a' is actually Int -> Float.

In the second definition, you're trying to give 'Int -> Float', but GHC only knows locally that you need to provide 'Int -> a' with a constraint 'Foo Int a' which it won't use to determine that a ~ Float.

This is not inherent to fundeps. One could make a version of fundeps that has the local constraint rules (easily so by translating to the new type families stuff). But, the difference is also the reason that overlapping instances are supported for fundeps and not type families. But I won't get into that right now.

I still don't understand what that mean. So still looking for better understandable answer.

like image 338
Satvik Avatar asked Sep 13 '12 13:09

Satvik


1 Answers

To avoid this problem, you can use type families instead:

{-# LANGUAGE TypeFamilies, FlexibleContexts #-}

class Fooo a where
  type Out a :: *

instance Fooo Int where
  type Out Int = Float
  
ff :: (Foo Int) => Int -> (Out Int)
ff = undefined

gg :: Int -> Float
gg= undefined

hh :: (Foo Int) => Int -> (Out Int)
hh = gg

Type families are nice. Use type families when you can!


I'm guessing your error is because ghc can deduce f :: Int -> Float from f :: Foo Int a => Int -> a and your class definition and instances, but it can't deduce g :: Foo Int a => Int -> a from g:: Int -> Float.

It's convenient to think of constraints as a secret dictionary parameter on the function. There's an inherent but constrained polymorphism in f that there isn't in g.

I think it's helpful to note that ghci is giving us essentially exactly the same error message as if we tried to define

j :: Int -> Float
j = undefined

k :: Eq a => Int -> a
k = j

It's obvious that this shouldn't work, because we know that k should be contrainedly polymorphic in its second argument. ghc tries to match the type Int -> a with Int -> Float and fails, complaining that it can't infer a ~ Float from the context Eq a, even though there's an instance Eq Float. In your example, it says it can't infer a ~ Float from the context Foo Int a even though there's an instance for Foo Int Float. I realise we can deduce that there's only one possible type for a, but by making the class and instance for Foo, you have defined a relation and asserted it's a functional dependency. This isn't the same as defining the function (which is why type families solves the problem - it defines the function).

ghc also complains when you write

aconst :: (Foo Int a) => a
aconst = 0.0

or even

anotherconst :: (Foo Int a) => a
anotherconst = 0.0::Float

always because it can't match the constrainedly polymorphic a it wanted with the specific type you gave it (Fractional a or Float).

You want

forall a.Foo Int a

to be the same type as Float, but it isn't. There's only one type that satisfies forall a.Foo Int a, it's Float, so ghci can take f::forall a.(Foo Int a)=>a->Float and deduce (using a dictionary for Foo) that f::Int->Float , but you're expecting ghci to take Float and find notice it's forall a.Foo Int a, but there's no dictionary for Float, it's a type, not a type class. ghci can do it one way, but not the other.

Dan's point about local information is that ghc would have to use both Foo's definition and the instance you made to deduce that Float can be rewritten forall a.(Foo Int a), and that at this point in compilation, ghc isn't using global information because it's just trying to do a match. My point is that Float matches forall a.(Foo Int a) but forall a.(Foo Int a) doesn't match Float, in the sense that "this" matches the pattern (x:xs) but (x:xs) doesn't match the pattern "this".

like image 168
AndrewC Avatar answered Oct 22 '22 15:10

AndrewC