5
votes

Upon trying to use Data.Has, I've been writing code like the following:

data Name = Name; type instance TypeOf Name = Text
type NameRecord = FieldOf Name;

I've found:

instance I NameRecord where
  ...

Throws a compile error, namely:

Illegal type synonym family application in instance

Whereas:

instance (NameRecord ~ a) => I a where
  ...

Compiles fine.

I believe the error is related to this ticket in GHC, marked as invalid.

The response to the ticket says:

I am not sure what you are suggesting. We cannot automatically transform

instance C (Fam Int) -- (1)

into

instance (Fam Int ~ famint) => C famint -- (2)

This works if there is only one instance, but as soon as there are two such instances, they always overlap.

Maybe you are suggesting that we should do it anyway and programmers should just take the implicit transformation into account. I don't think that this is a good idea. It's confusing for very little benefit (as you can always write the transformed instance yourself with little effort).

Can someone elaborate on this explanation, perhaps with some sample code where the (1) fails but (2) does not, and why?

1

1 Answers

4
votes

A case where (1) fails but (2) does not is trivial; because type synonyms (type ExampleOfATypeSynonym = ...) are not allowed in instance declarations, but they are allowed in constraints, any situation where you have only one instance like this:

-- (1)
class Foo a
type Bla = ()
instance Foo Bla

... can be transformed into:

-- (2)
class Foo a
type Bla = ()
instance (a ~ Bla) => Foo a

The only reason why (1) fails is because type synonyms are not allowed in instance declarations, and that's because type synonyms are like type functions: they provide a one-way mapping from a type name to a type name, so if you have a type B = A and an instance Foo B, it is non-obvious that an instance of Foo A is created instead. The rule exists so that you have to write instance Foo A instead to make it clear that that is the type that actually gets the instance.

The use of type families is irrelevant in this context, because the problem is rather that you're using a type synonym, the NameRecord type. You have to also keep in mind that if the type synonym is removed and replaced by FieldOf Name directly, the compile will still fail; that is because a "type family" is just an enhanced version of type synonyms, so FieldOf Name is also a "type synonym" for Name :> Text in this context. You have to use a data family and a data instance instead to get a "bidirectional" association.

More information about data families can be found in the GHC documentation.


I think you mean "... where (2) fails but (1) does not..."

Let's imagine that we have a type class like so:

class Foo a where
  foo :: a

Now, you can write instances like so:

 instance Foo Int where
   foo = 0

 instance Foo Float where
   foo = 0

 main :: IO ()
 main = print (foo :: Float)

This works as one would expect. However, if you transform the code into this:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
class Foo a where
  foo :: a

instance (a ~ Int) => Foo a where
  foo = 0

instance (a ~ Float) => Foo a where
  foo = 0

main :: IO ()
main = print (foo :: Float)

It doesn't compile; it displays the error:

test.hs:5:10:
    Duplicate instance declarations:
      instance a ~ Int => Foo a -- Defined at test.hs:5:10-27
      instance a ~ Float => Foo a -- Defined at test.hs:8:10-29

So, this is the example you hopefully were looking for. Now, this only happens if there is more than one instance of Foo that uses this trick. Why is that?

When GHC resolves type classes, it only looks at the instance declaration head; i.e. it ignores everything before the =>. When it has chosen an instance, it "commits" to it, and checks the constraints before the => to see whether they are true. So, at first it sees two instances:

instance Foo a where ...
instance Foo a where ...

It is clearly impossible to decide which instance to use based on this information alone.