How type inference works in the presence of functional dependencies

Consider the code below:

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

Now that I see the supposed type f in ghci

 > :tf > f :: Int -> Float 

Now, if I add the following code

 g :: Int -> Float g = undefined h :: (Foo Int a) => Int -> ah = g 

I get an error

 Could not deduce (a ~ Float) 

I canโ€™t understand what happened here? The restriction Foo Int a was to restrict the type h to Int -> Float , as shown in the deduced type f .

Is it because type resolution happens to resolve instances?

[Update]

An explanation given by Dan Doel on the cafe mailing list

The answer, I believe, is that the difference between fundep implementation families and types is local data constraints. The funds do not have any local distribution.

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

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

This is not inherent in foundations. It would be possible to make a version of fundeps that has local restriction rules (easily so, translating into a new type of family). But the difference is also the reason that overlapping instances are supported for drive types, not types. But I will not go into it right now.

I still don't understand what that means. So we are looking for a more understandable answer.

+7
source share
1 answer

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 

Typical families are nice. Use family types when you can!


I assume that your error is related to the fact that ghc can output f :: Int -> Float from f :: Foo Int a => Int -> a and the definition of your class and instances, but cannot output g :: Foo Int a => Int -> a from g:: Int -> Float .

It is convenient to think of constraints as the secret parameter of a dictionary for a function. There is a characteristic but limited polymorphism in f, which is absent in g.

I find it helpful to note that ghci gives us almost the same error message as if we were trying to determine

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

Obviously, this should not work, because we know that k must be a proactive polymorphism in the second argument. ghc attempts to map the type Int -> a to Int -> Float and fails, stating that it cannot infer a ~ Float from the context of Eq a , although there is an instance of Eq Float . In your example, he says that he cannot get a ~ Float out of the context of Foo Int a , although there is an instance for Foo Int Float . I understand that we can conclude that for a there is only one possible type, but by creating a class and an instance for Foo, you defined the relation and approved it as a functional dependency. This is not the same as defining a function (therefore type families solve the problem - it defines a 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 cannot be matched with the limited polymorphic a , which is required for the specific type that you gave it ( Fractional a or Float ).

Do you want to

 forall a.Foo Int a 

- The same type as Float , but it is not. There is only one type that satisfies forall a.Foo Int a , it Float , so ghci can take f::forall a.(Foo Int a)=>a->Float and output (using the dictionary for Foo) that f::Int->Float , but you expect ghci to take Float and find the notification it forall a.Foo Int a , but there is no dictionary for Float, it's a type, not a type. ghci can do this in one way, but not in another.

Given a point about local information is that ghc will have to use the definition of Foo and the instance you made to deduce that Float can be rewritten forall a.(Foo Int a) , and that at this point in the ghc compilation without using global information because itโ€™s just trying to make a match. I want to say 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) does not match the "this" pattern.

+4
source

All Articles