I'm trying to learn about how to use GHC.Generics. A fascinating topic but daunting.
While reading through the blog entry 24 Days of GHC Extensions: DeriveGeneric, I learned how to take a value and navigate its Rep. Okay.
However, reading the blog entry Building data constructors with GHC Generics which describes the analog of constructing the Rep and converting it back to a value, I got stumped. I've read through a number of other resources, but to no great help.
In the blog entry is the following code. First, constructing the Rep:
class Functor f => Mk rep f | rep -> f where
mk :: f (rep a)
instance Mk (K1 i c) ((->) c) where
mk = \x -> K1 x
instance (Mk l fl, Mk r fr) => Mk (l :*: r) (Compose fl fr) where
mk = Compose (fmap (\l -> fmap (\r -> l :*: r) mk) mk)
instance (Mk f f') => Mk (M1 i c f) f' where
mk = M1 <$> mk
Then, dealing with the Compose:
class Functor f => Apply f a b | f a -> b where
apply :: f a -> b
instance Apply ((->) a) b (a -> b) where
apply = id
instance (Apply g a b, Apply f b c) => Apply (Compose f g) a c where
apply (Compose x) = apply (fmap apply x)
Then dealing with type ambiguity:
type family Returns (f :: *) :: * where
Returns (a -> b) = Returns b
Returns r = r
make :: forall b f z. (Generic (Returns b), Apply f (Returns b) b, Mk (Rep (Returns b)) f) => b
make = apply (fmap (to :: Rep (Returns b) z -> (Returns b)) (mk :: f (Rep (Returns b) z)))
Wow.
Really, I'm stuck at the very beginning, at the class Mk where mk returns a functor. My questions:
What is
mkreturning? Why is it a functor? What is the interpretation of its result? I can see that theK1 i cinstance ofMkreturns a function (I understand this is a functor) that takes a value and wraps it inK1, butmkforMk (l :*: r)andMk (M1 i c f)are completely lost on me.I'm guessing
Composecomes fromData.Functor.Compose, which means that when I dofmap f x, it does thefmaptwo levels deep into the composed functors. But I can't make sense of the nestedfmaps inside theCompose.For the instance of
M1 i c f, I thought it would just wrap the inner values inM1, so the need toM1 <$> mkorfmap M1 mkmakes no sense to me.
Obviously I'm not grokking the intent or meaning of these instances and how these instances interact to create the final Rep. I am hoping someone can enlighten me and provide a good explanation of how to use GHC.Generics along the way.
Let's go through a much simpler example In the documentation of GHC.Generics first. To achieve a generic function
encode :: Generic a => a -> [Bool]that bit serialize every data type which has a Generic instance, they defined the type class below :By defining
Encode'instances for every Rep type (M1, K1, etc.), they made the function work universally on every data types.In Building data constructors with GHC Generics, the author's final goal is a generic function
make :: Generic a => TypeOfConstructor a, so naively one may define:And soon realize that it's impossible due to a few problems:
->, the function type in haskell only takes one argument at a time, somkwon't be able to return anything sensible if the constructor takes more than one argument.reptype in concern.pas the result type. Without therepcontext it's impossible to derive instances for:*:or:+:, and the function will no longer work on any nested data type.Issue 1 can be solved with Data.Functor.Compose. A function of type
a -> b -> ccan be encoded intoCompose ((->) a) ((->) b) c, it can be further composed while keeps a whole lot of information about argument types. And by making it a type parameter ofMk, issue 2 is solved too:where
fis generalization overCompose f gand(->) a, which contains type-level information to construct arep p, i.e. everything before the final->ina -> b -> c -> ... -> rep p.In the
Mkinstance of:*::fmapchanges only the inner-most type of a nested Compose, in this case changes the final result of a n-ary function.mkhere is literally concatenating two argument listsflandfr, putting their results into a product type, namelyIt does just wrap the inner values in
M1, but it's unclear how long the argument list of the underlyingfis. If it takes one argument thenmkis a function, otherwise it's a Compose.fmapwraps the inner-most value of them both.