I started with this type for leaf-valued trees with labeled nodes:
type Label = String
data Tree a = Leaf Label a
| Branch Label [Tree a]
I have some folds I'd like to write over this tree, and they all take the form of catamorphisms, so let's let recursion-schemes
do the recursive traversal for me:
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable, TemplateHaskell, TypeFamilies #-}
import Data.Functor.Foldable.TH (makeBaseFunctor)
import Data.Functor.Foldable (cata)
type Label = String
data Tree a = Leaf Label a
| Branch Label [Tree a]
makeBaseFunctor ''Tree
allLabels :: Tree a -> [Label]
allLabels = cata go
where go (LeafF l _) = [l]
go (BranchF l lss) = l : concat lss
And all is well: we can traverse a tree:
λ> allLabels (Branch "root" [(Leaf "a" 1), Branch "b" [Leaf "inner" 2]])
["root","a","b","inner"]
But that definition of Tree is a little clunky: each data constructor needs to handle the Label separately. For a small structure like Tree this isn't too bad, but with more constructors it would be quite a nuisance. So let's make the labeling its own layer:
data Node' a = Leaf' a
| Branch' [Tree' a]
data Labeled a = Labeled Label a
newtype Tree' a = Tree' (Labeled (Node' a))
makeBaseFunctor ''Tree'
makeBaseFunctor ''Node'
Great, now our Node type represents the structure of a tree without labels, and Tree' and Labeled conspire to decorate it with labels. But I no longer know how to use cata
with these types, even though they are isomorphic to the original Tree
type. makeBaseFunctor
doesn't see any recursion, so it just defines base functors that are identical to the original types:
$ stack build --ghc-options -ddump-splices
...
newtype Tree'F a r = Tree'F (Labeled (Node' a))
...
data Node'F a r = Leaf'F a | Branch'F [Tree' a]
Which like, fair enough, I don't know what I'd want it to generate either: cata
expects a single type to pattern-match on, and of course it can't synthesize one that's a combination of two of my types.
So what's the plan here? Is there some adaptation of cata
that works here if I define my own Functor instances? Or a better way to define this type that avoids duplicate handling of Label but still is self-recursive instead of mutually recursive?
I think this question is probably related to Recursion schemes with several types, but I don't understand the answer there: Cofree
is so far mysterious to me, and I can't tell whether it's essential to the problem or just a part of the representation used; and the types in that question are not quite mutally-recursive, so I don't know how to apply the solution there to my types.