Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous type with type families

I am running into a problem where GHC is not able to match Foo t and Foo t0, where it definitely looks to me like t ~ t0. Here's a minimal example:

{-# LANGUAGE GADTs          #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies   #-}

data Foobar :: * -> * where
  Foobar :: Foo t -> Foobar t

type family Foo a :: *

class Bar t where
  f :: Foobar t
  g :: Foo t
--  f = Foobar g

When uncommenting the last line, GHC complains:

Couldn't match expected type ‘Foo t’ with actual type ‘Foo t0’
NB: ‘Foo’ is a type function, and may not be injective
The type variable ‘t0’ is ambiguous
Relevant bindings include f :: Foobar t (bound at test.hs:13:3)
In the first argument of ‘Foobar’, namely ‘g’
In the expression: Foobar g

I understand that Foo is not injective, but from my analysis GHC is never asked to come up with t from Foo t. It seems that the type t is lost in Foobar g, and so it can't match the inital Foo t and the new Foo t0. Is the context here not enough? Am I missing a case where f = Foobar g could not yield the correct result?

Any help appreciated!

N.B.: ScopedTypeVariables and explicit type signatures don't seem to help. Adding the constraint a ~ Foo t and using a instead as the type of g in Foobar g doesn't work either.

Looks a lot like: ambiguous type error when using type families, and STV is not helping. I considered using a Proxy, but I feel like in this case GHC should be able to figure it out.

like image 608
Nicolas Mattia Avatar asked Jun 13 '16 14:06

Nicolas Mattia


1 Answers

I understand that Foo is not injective, but from my analysis GHC is never asked to come up with t from Foo t.

So, you realize the ambiguity. Let's make is explicit:

type instance Foo ()   = Bool
type instance Foo Char = Bool

instance Bar () where
   -- I omit the declaration for f
   g = True
instance Bar Char where
   g = False

main = print (g :: Bool)

Your problem in f = Foobar g is related to the ambiguity.

The crux is: the definition f = Foobar g does not imply that g much be picked in the same instance as f. It could refer to a different instance!

Consider

show (x,y) = "(" ++ show x ++ ", " ++ show y ++ ")"

Above, the RHS uses of show are from a different instance from the one where the LHS show is.

In your f = Foobar g line, GHC infers that g :: Foo t where t is the same index of the f instance. However, that is not enough to select an instance for g! Indeed, we might have Foo t ~ Foo t0 for some other t0, hence g might refer to the g in the t0 instance, causing an ambiguity error.

Note that your code is rejected by GHC 8 even if the last line is commented out, because the type of g is inherently ambiguous:

• Couldn't match expected type ‘Foo t’ with actual type ‘Foo t0’
  NB: ‘Foo’ is a type function, and may not be injective
  The type variable ‘t0’ is ambiguous
• In the ambiguity check for ‘g’
  To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
  When checking the class method: g :: forall t. Bar t => Foo t
  In the class declaration for ‘Bar’

We can follow the suggestion to make GHC 8 more lenient, like GHC 7. This will make your code type check, until we uncomment the last line.

• Couldn't match expected type ‘Foo t’ with actual type ‘Foo t0’
      NB: ‘Foo’ is a type function, and may not be injective
      The type variable ‘t0’ is ambiguous

This is the same error you saw in GHC 7. In GHC 8, we have another luxury: we can explicitly choose the t for g as follows:

class Bar t where
  f :: Foobar t
  f = Foobar (g @ t)
  g :: Foo t

This requires a few more extensions to be turned on, of course. In GHC 7, you need a proxy to be able to select an instance unambiguously.

like image 183
chi Avatar answered Nov 15 '22 08:11

chi