Search code examples
haskellmetaprogramminggeneric-programminghigher-kinded-types

Automatically generate mapping function for data with higher-kinded parameter


Consider the data type

data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}

I would like a function mapFoo :: (forall a. f a -> g a) -> Foo f -> Foo g. My options:

  • I could write it manually. This is mildly annoying, but the killer objection is that I expect Foo to gain fields over time and I want that to be as frictionless as possible, so having to add a case to this function is annoying.
  • I could write Template Haskell. I'm pretty sure this isn't too hard, but I tend to view TH as a last resort, so I'm hoping for something better.
  • Could I use generics? I derived Generic, but when I tried to implement the K1 case (specifically to handle Rec0) I couldn't figure out how to do it; I needed it to change the type.
  • Is there a fourth option that I just missed?

If there is a generic way to write mapFoo without reaching for Template Haskell, I'd love to know about it! Thanks.


Solution

  • The rank2classes package can derive this for you.

    {-# LANGUAGE TemplateHaskell #-}
    
    import Rank2.TH (deriveFunctor)
    
    data Foo f = Foo {fooInt :: f Int, fooBool :: f Bool}
    
    $(deriveFunctor ''Foo)
    

    Now you can write mapFoo = Rank2.(<$>).