Open lierdakil opened 2 years ago
This is indeed a bug. Currently, makeRep0Inline
really only works if you're writing an instance of Generic (T a_1 ... a_n)
, where a_1 ... a_n
are all distinct type variables. (This isn't clear at all from reading the Haddocks for makeRep0Inline
, unfortunately.) Your instance would likely work if you wrote Generic (SomeData a)
instead of Generic (SomeData OtherData)
. Alternatively, you could use deriving instance Generic (SomeData OtherData)
.
Is this bug fixable? In some cases, perhaps. It would be annoying to make it work in the general case, however. Consider this example:
newtype S (a :: k) = MkS k (Proxy a)
instance Generic (S Bool) where
type Rep (S Bool) = $(makeRep0Inline ''S [t| S Bool |])
...
Currently, makeRep0Inline
generates a Rep
instance that looks (approximately) like Rec0 k :*: Rec0 (Proxy a)
. In order to be correct, however, it would really need to be Rec0 Type :*: Rec0 (Proxy Bool)
. In other words, we'd somehow need to compute a substitution that maps a
to Bool
and k
to Type
. Figuring out that a
maps to Bool
would be straightforward enough, but figuring out that k
maps to Type
would be tricky, since the original program does not mention Type
anywhere. You'd have to perform type inference in Template Haskell to accomplish this, and doing type inference in TH is notoriously tricky.
You'd have to perform type inference in Template Haskell to accomplish this
What if we specify kinds explicitly? E.g.
type Rep (S Bool) = $(makeRep0Inline ''S [t| S (Bool :: Type) |])
It would still be rather fragile. The reason that makeRep0Inline
works today is because it gathers all of the type variables it encounters within [t| ... |]
from left-to-right. It's dumb, but very predictable.
When you're allowed to instantiate type variables, however, things become trickier. Consider [t| T (Bool :: Type) (Char :: Type) |]
, for instance. Which of those types are instantiating type variables? That depends on what the definition of T
is. You'd get entirely different substitutions depending on whether you define T
as T (a :: Type) (b :: Type)
, T (a :: j) (b :: Type)
, T (a :: Type) (b :: k)
, or T (a :: j) (b :: k)
. To put it differently: you'd have to remember the positions of each type variable in the original data type declaration and somehow figure out what types appear in the same positions in the [t| ... |]
type.
Again, I don't think this would be impossible to do, but it require a great deal of care to make sure the implementation is right.
I've made a small project illustrating the issue: https://github.com/lierdakil/generic-deriving-issue
Given some types
I'm trying to make an instance:
This, however, fails with a pretty confusing error message:
-ddump-splices
is indeed useful here:You might notice, the splice is referencing a type variable
a_i2J2
, which is not on the left-hand side of the type instance definition (because I've used a concrete type,OtherData
as an argument).The expectation that this should work. But it doesn't, likely due to a bug or an oversight. Maybe I'm misunderstanding something. If so, please explain.
Cheers!