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
?
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 exampleinstance C a => C (a,b) where ... data T a b = MkT a (a,b) deriving( C )
The
deriving
clause will generateinstance C a => C (T a b) where {}
The constraints
C a
andC (a,b)
are generated from the data constructor arguments, but the latter simplifies toC 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With