What is the difference between `DeriveAnyClass` and an empty instance?
Asked Answered
J

1

24

Using the cassava package, the following compiles:

{-# LANGUAGE DeriveGeneric #-}

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic)
instance ToNamedRecord Foo

However, the following does not:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic, ToNamedRecord)

The compiler reports:

test.hs:7:50:
    No instance for (ToNamedRecord Int)
      arising from the first field of ‘Foo’ (type ‘Int’)
    Possible fix:
      use a standalone 'deriving instance' declaration,
        so you can specify the instance context yourself
    When deriving the instance for (ToNamedRecord Foo)

This leaves me with two questions: Why isn't the second version identical to the first? And why is the compiler hoping to find an instance for ToNamedRecord Int?

Jan answered 7/9, 2016 at 21:6 Comment(3)
I haven't yet seen DeriveAnyClass do anything useful. I have seen it produce compile-time crashes, however. Methinks 'tis buggy.Bollard
These days, the code proposed in this question does compile! The compiler's behavior has changed -- and for the better.Jan
Indeed, the extension got a lot of work over the last couple years, I believe mostly by Ryan Scott.Bollard
T
15

NB: As pointed out by David in the comments, GHC has been updated since I wrote this. The code as written in the question compiles and works correctly. So just imagine everything below is written in the past tense.


The GHC docs say:

The instance context will be generated according to the same rules used when deriving Eq (if the kind of the type is *), or the rules for Functor (if the kind of the type is (* -> *)). For example

instance C a => C (a,b) where ...

data T a b = MkT a (a,b) deriving( C )

The deriving clause will generate

instance C a => C (T a b) where {}

The constraints C a and C (a,b) are generated from the data constructor arguments, but the latter simplifies to C a.

So, according to the Eq rules, your deriving clause generates...

instance ToNamedRecord Int => ToNamedRecord Foo where

... which is not the same as...

instance ToNamedRecord Foo where

... in that the former is only valid if there's an instance ToNamedRecord Int in scope (which is appears there isn't in your case).

But I find the spec to be somewhat ambiguous. Should the example really generate that code, or should it generate instance (C a, C (a, b)) => instance C (T a b) and let the solver discharge the second constraint? It appears, in your example, that it's generating such constraints even for fields with fully-concrete types.

I hesitate to call this a bug, because it's how Eq works, but given that DeriveAnyClass is intended to make it quicker to write empty instances it does seem unintuitive.

Trinomial answered 7/9, 2016 at 23:10 Comment(4)
Thanks, this explains things completely! Now that you've highlighted the problem that's being solved ("What context should be given to the instance?"), I can see why the GHC folks made the decision they did -- and I'm nevertheless starting to consider this a misfeature. The conditions under which it will "Just Work" seem incredibly specific, and the work it saves in those conditions seems quite small indeed.Jan
I agree. Most (all?) of the classes for which DeriveAnyClass is useful will be relying on a top-level superclass like Generic or Data, rather than a recursive context based on the structure of the type. The rules make sense for Eq because the generated code is itself structurally recursive.Trinomial
This answer is now outdated. As Daniel Wagner points out in a comment on the question, the code now compiles! This answer should at least be updated to indicate that, and to link to an older version of the manual that still says that Eq and Functor business. Recent versions are much more sensible about inferring constraints.Bollard
@Bollard Thanks for notifying me, I've added a note to the top of the answerTrinomial

© 2022 - 2024 — McMap. All rights reserved.