3

So, I have an AST data type with a large number of cases, which is parameterized by an "annotation" type

data Expr a = Plus a Int Int
    | ...
    | Times a Int Int

I have annotation types S and T, and some function f :: S -> T. I want to take an Expr S and convert it to an Expr T using my conversion f on each S which occurs within an Expr value.

Is there a way to do this using SYB or generics and avoid having to pattern match on every case? It seems like the type of thing that this is suited for. I just am not familiar enough with SYB to know the specific way to do it.

jmite
  • 8,171
  • 6
  • 40
  • 81
  • 6
    sounds like a lot like a `Functor`. The `fmap` function has the type `Functor f => (a -> b) -> (f a -> f b)`. If you say that `Expr` is a `Functor`, then `fmap :: (a -> b) -> (Expr a -> Expr b)`. – bheklilr Dec 28 '14 at 06:10
  • 5
    If you want to avoid the boilerplate of the writing `Functor` definition too, you can use the `DeriveFunctor` extension. – David Young Dec 28 '14 at 06:23
  • Exactly what I was looking for. Write it as an answer and I'll accept it. Thanks! – jmite Dec 28 '14 at 06:41

2 Answers2

6

It sounds like you want a Functor instance. This can be automatically derived by GHC using the DeriveFunctor extension.

David Young
  • 10,713
  • 2
  • 33
  • 47
2

Based on your follow-up question, it seems that a generics library is more appropriate to your situation than Functor. I'd recommend just using the function given on SYB's wiki page:

{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, FlexibleContexts #-}
import Data.Generics
import Unsafe.Coerce

newtype C a = C a deriving (Data,Typeable)

fmapData :: forall t a b. (Typeable a, Data (t (C a)), Data (t a)) =>
    (a -> b) -> t a -> t b
fmapData f input = uc . everywhere (mkT $ \(x::C a) -> uc (f (uc x)))
                    $ (uc input :: t (C a))
    where uc = unsafeCoerce

The reason for the extra C type is to avoid a problematic corner case where there are occurrences of fields at the same type as a (more details on the wiki). The caller of fmapData doesn't need to ever see it.

This function does have a few extra requirements compared to the real fmap: there must be instances of Typeable for a, and Data for t a. In your case t a is Expr a, which means that you'll need to add a deriving Data to the definition of Expr, as well as have a Data instance in scope for whatever a you're using.

John L
  • 27,937
  • 4
  • 73
  • 88