How to construct generic Functor instances using GHC.Generics (or other similar frameworks)?
Asked Answered
C

2

9

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.)

Crispy answered 19/7, 2013 at 16:17 Comment(0)
C
9

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.

Certify answered 19/7, 2013 at 20:1 Comment(0)
B
3

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.

Bewitch answered 19/7, 2013 at 17:40 Comment(5)
Does GHC derive instances of Generic1 automatically?Crispy
@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.