3
votes

I have the following code

{-# LANGUAGE PolyKinds, DefaultSignatures, FlexibleContexts, DeriveAnyClass, DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}
module DeriveTest where

import GHC.Generics

class GenericClass a m where
instance GenericClass f m => GenericClass (M1 i c f) m
instance Condition a m => GenericClass (K1 i a) m

class Condition (a :: k) (m :: * -> *) where
instance (Condition a m, Condition b m) => Condition (a b) m
instance {-# OVERLAPPABLE #-} Condition (a :: k) m

class Class (e :: (* -> *) -> *) where
    classF :: e m -> ()
    default classF :: GenericClass (Rep (e m)) m => e m -> ()
    classF = undefined

It defines the class Class of types that have a higher-kinded type as a parameter. It also defines a generic way to derive an instance of that class. Now if I declare a new datatype like this, and try to derive an instance of Class

data T a m = T
    { field :: a }
    deriving (Generic, Class)

I get the following error:

    * Overlapping instances for Condition a m
        arising from the 'deriving' clause of a data type declaration
      Matching instances:
        instance [overlappable] forall k (a :: k) (m :: * -> *).
                                Condition a m
        instance forall k1 k2 (a :: k1 -> k2) (m :: * -> *) (b :: k1).
                 (Condition a m, Condition b m) =>
                 Condition (a b) m
      (The choice depends on the instantiation of `a, m'
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    * When deriving the instance for (Class (T a))
   |
22 |     deriving (Generic, Class)
   |                        ^^^^^

The error sort of makes sense because I guess. The instance really does depend on the instantiation of a. However, if I just write an empty instance like this:

data T a m = T
    { field :: a }
    deriving (Generic)
instance Class (T a) -- works

It works. Why? And how can I make it behave the same with the deriving statement?

This is using GHC 8.2.2

1
@dfeuer Ok, did it.Luka Horvat

1 Answers

1
votes

I don't think DeriveAnyClass is to blame. I believe the real culprit is that GHC's unpredictable behavior surrounding overlapping instances. To see what I mean, let's factor out the default implementation of classF into its own function:

class Class (e :: (* -> *) -> *) where
    classF :: e m -> ()
    default classF :: GenericClass (Rep (e m)) m => e m -> ()
    classF = classFDefault

classFDefault :: forall (e :: (* -> *) -> *) (m :: * -> *).
                 GenericClass (Rep (e m)) m => e m -> ()
classFDefault = undefined

Now, given your definition of T:

data T a m = T
    { field :: a }
    deriving (Generic)

Observe that this typechecks:

instance Class (T a) where
  classF = classFDefault

But this doesn't!

classFT :: forall a (m :: * -> *).
           T a m -> ()
classFT = classFDefault

The latter fails with the same error as you would get had you attempted to derive Class with a deriving clause. Unfortunately, I have no idea why GHC accepts the former but rejects the latter, so I can only surmise that GHC is rather picky about how overlapping instances are used when typechecking, and GHC happens to get into a bad mood when it tries to solve a Condition a m constraint in a certain way.

It may be worth filing a bug on the GHC issue tracker about this.