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.
N.B.: be careful where
Cofreeand:<are imported from. One is fromControl.Comonad.Cofree, the other fromControl.Comonad.Trans.Cofree(as part of theCofreeFdata type).If you import
Cofreefrom....Trans.Cofreeinstead, thecatalooks like this instead: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 ofcata:Here
t ~ Cofree f aand the type instance ofBaseforCofreegives:Where
CofreeFis:i.e., a fancy pair type. Let's replace it with an actual pair type to make things clearer:
Now we're really specializing
catawith a more concreteb, namelyFix f:forgetis parametric inaandf, so the function we givecatacan do nothing with theain the pair, and the only sensible way to implement the remainingf (Fix f) -> Fix fis theFixwrapper.Operationally,
Fixis 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).