I'm trying to learn GHC Generics. After reviewing several examples, I wanted to try to create a generic Functor
instances (disregarding that GHC can derive them automatically for me). However, I realized I have no idea how to work with a parametrized data types with Generics, all the examples I've seen were of kind *
. Is this possible, and if yes, how? (I'm also interested in other similar frameworks, such as SYB.)
How to construct generic Functor instances using GHC.Generics (or other similar frameworks)?
Asked Answered
The best place to look for lots of example functions using GHC Generics is the generic-deriving
package. There's a generic definition of the Functor
class in there. Copying (slightly simplified) from Generics.Deriving.Functor
:
class GFunctor' f where
gmap' :: (a -> b) -> f a -> f b
instance GFunctor' U1 where
gmap' _ U1 = U1
instance GFunctor' Par1 where
gmap' f (Par1 a) = Par1 (f a)
instance GFunctor' (K1 i c) where
gmap' _ (K1 a) = K1 a
instance (GFunctor f) => GFunctor' (Rec1 f) where
gmap' f (Rec1 a) = Rec1 (gmap f a)
instance (GFunctor' f) => GFunctor' (M1 i c f) where
gmap' f (M1 a) = M1 (gmap' f a)
instance (GFunctor' f, GFunctor' g) => GFunctor' (f :+: g) where
gmap' f (L1 a) = L1 (gmap' f a)
gmap' f (R1 a) = R1 (gmap' f a)
instance (GFunctor' f, GFunctor' g) => GFunctor' (f :*: g) where
gmap' f (a :*: b) = gmap' f a :*: gmap' f b
instance (GFunctor f, GFunctor' g) => GFunctor' (f :.: g) where
gmap' f (Comp1 x) = Comp1 (gmap (gmap' f) x)
class GFunctor f where
gmap :: (a -> b) -> f a -> f b
default gmap :: (Generic1 f, GFunctor' (Rep1 f))
=> (a -> b) -> f a -> f b
gmap = gmapdefault
gmapdefault :: (Generic1 f, GFunctor' (Rep1 f))
=> (a -> b) -> f a -> f b
gmapdefault f = to1 . gmap' f . from1
To use this on a datatype, you have to derive Generic1
rather than Generic
. The key difference of the Generic1
representation is that it makes use of the Par1
datatype that encodes parameter positions.
There is a Generic1
class for data types of kind * -> *
. Working with it is mostly the same as with data types of kind *
, except there's also Par1
for the parameter. I've used it in my unfoldable package for example.
@PetrPudlák Not fully automatically. But with the
DeriveGeneric
language extension, you can use deriving Generic
as well as deriving Generic1
(where the latter only works for datatypes with at least one parameter, the last parameter being of kind *
). –
Certify @Certify Thank you. Unfortunately for my goal I'd like to work with more complex kinds, so probably I'll have to use Template Haskell. –
Crispy
@PetrPudlák Ah, but your question was just about the normal Functor class. If you want more complex kinds, then perhaps you can explain what you really want. –
Certify
@Certify Yes, I wanted to start with a simpler example like
Functor
and I'm definitely glad for all the information. I'll process it and then perhaps ask another question. –
Crispy © 2022 - 2024 — McMap. All rights reserved.
Generic1
automatically? – Crispy