3

I have an AST that I'm annotating using Cofree:

data ExprF a
  = Const Int
  | Add a
        a
  | Mul a
        a
  deriving (Show, Eq, Functor)

I use type Expr = Fix ExprF to represent untagged ASTs, and type AnnExpr a = Cofree ExprF a to represent tagged ones. I've figured out a function to transform tagged ASTs into untagged ones by throwing away all the annotations:

forget :: Functor f => Cofree f a -> Fix f
forget = Fix . fmap uncofree . unwrap

This looks like it might be some sort of catamorphism (I'm using the definition from Kmett's recursion-schemes package).

cata :: (Base t a -> a) -> t -> a
cata f = c where c = f . fmap c . project

I'd think the above rewritten using a catamorphism would look something like this, but I can't figure out what to put for alg to make it typecheck.

forget :: Functor f => Cofree f a -> Fix f
forget = cata alg where
  alg = ???

Any help figuring out if this really is a cata/anamorphism, and some intuition for why it is/isn't would be greatly appreciated.

xgrommx
  • 461
  • 3
  • 15
xal
  • 127
  • 1
  • 5
  • In order to use `cata` for this, you need to write an algebra from the pattern functor of `Cofree` (that is, `CofreeF`) to `Fix ExprF`. Looks like `tailF` would be helpful for this: https://hackage.haskell.org/package/free-4.12.4/docs/src/Control-Comonad-Trans-Cofree.html#tailF If nobody else responds before, I'll answer with more details soon. – Juan Pablo Santos Jun 26 '18 at 19:03

1 Answers1

6
forget :: Functor f => Cofree f a -> Fix f
forget = cata (\(_ :< z) -> Fix z)
-- (Control.Comonad.Trans.Cofree.:<)
-- not to be confused with
-- (Control.Comonad.Cofree.:<)

Explanation

Looking only at the types, we can show that there is really only one way to implement forget. Let's start with the type of cata:

cata :: Recursive t => (Base t b -> b) -> t -> b

Here t ~ Cofree f a and the type instance of Base for Cofree gives:

type instance Base (Cofree f a) = CofreeF f a

Where CofreeF is:

data CoFreeF f a b = a :< f b
-- N.B.: CoFree also defines a (:<) constructor so you have to be
-- careful with imports.

i.e., a fancy pair type. Let's replace it with an actual pair type to make things clearer:

cata :: Functor f => ((a, f b) -> b) -> Cofree f a -> b

Now we're really specializing cata with a more concrete b, namely Fix f:

-- expected type of `cata` in `forget`
cata :: Functor f => ((a, f (Fix f)) -> Fix f) -> Cofree f a -> Fix f

forget is parametric in a and f, so the function we give cata can do nothing with the a in the pair, and the only sensible way to implement the remaining f (Fix f) -> Fix f is the Fix wrapper.

Operationally, Fix is the identity, so (\(_ :< z) -> Fix z) is really (\(_ :< z) -> z) which corresponds to the intuition of removing the annotation, i.e., the first component of the pair (_ :< z).

Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • 1
    I knew I'd gotten something wrong with the `Cofree` vs. `CofreeF` situation! All I needed to do was change the pattern match on `:<` to a pattern match on `:<` from the `Control.Comonad.Trans.Cofree` module. Thank you! – xal Jun 26 '18 at 20:02
  • Indeed, you were very close! – Li-yao Xia Jun 26 '18 at 20:44