'Is there a name for mapping recursive data types based on terminals and their ancestries?
Let's say I have a type that looks like this:
data Term a = Terminal a
| Application (Term a) (Term a)
| Abstraction String (Term a)
Now, I want to map Term a to Term b. Ideally, I would be able to do this using a function (a -> b) and just implement fmap. However, that doesn't work for me. The map from Terminal a to Terminal b is dependent not just on the value of a, but also the values of the ancestors of Terminal a (e.g. the depth of Terminal a). So it's more like [Term a] -> b which is messy, so I'm trying to decompose this into something cleaner.
So really, what I need is something like 2 functions and an initial value: (c -> Term a -> c) can be called on each of the ancestors in order to accumulate whatever we want. (I guess it's equivalent to ([Term a] -> c) but I'm not sure whether that confuses the situation or helps.) (c -> a -> b) can map Terminal a to Terminal b. (We also need an initial value of type c)
So I'm definition a function with the following type signature:
notQuiteANatTrans :: (c -> Term a -> c) -> (c -> a -> b) -> c -> Term a -> Term b
This isn't a natural transformation. (I don't think) Or if it is, it's mapping something like [Term a] -> [Term b] where each of those is the path from the root of the tree to the Terminal. Is there a name for this? Is there maybe a different way to decompose my arrows in order to get something cleaner?
Solution 1:[1]
I'm not sure I entirely understand the problem, so apologies if the following goes off on an irrelevant tangent...
The map from
Terminal atoTerminal bis dependent not just on the value ofa, but also the values of the ancestors ofTerminal a(e.g. the depth ofTerminal a).
This sounds reminiscent of having to examine a tree to find, for example, its depth. For a tree, you can do this with its catamorphism - see e.g. the examples for foldTree.
In general, if you know the catamorphism for a datatype, you can derive most other (perhaps all?) useful functions from it. So what's the catamorphism for Term a?
F-Algebra
You can derive the catamorphism from F-Algebras. I'll follow the process I've used in my own article series on catamorphisms.
Define the underlying endofunctor like this:
data TermF a c =
TerminalF a
| ApplicationF c c
| AbstractionF String c
deriving (Eq, Show)
instance Functor (TermF a) where
fmap _ (TerminalF a) = TerminalF a
fmap f (ApplicationF x y) = ApplicationF (f x) (f y)
fmap f (AbstractionF s x) = AbstractionF s (f x)
This enables you to figure out the catamorphism by using typed holes:
termF = cata alg
where alg (TerminalF x) = _
alg (ApplicationF x y) = _
alg (AbstractionF s c) = _
If you try to compile this sketch, the compiler will complain about the type holes and tell you what you need. I used this process to arrive at this function:
termF :: (a -> c) -> (c -> c -> c) -> (String -> c -> c) -> Fix (TermF a) -> c
termF t appl abst = cata alg
where alg (TerminalF x) = t x
alg (ApplicationF x y) = appl x y
alg (AbstractionF s x) = abst s x
The catamorphism requires a mapper of the underlying type a -> c, as well as 'accumulators' for each of the recursive nodes.
Isomorphism
Fix (TermF a) is isomorphic with Term a, as witnessed by these two conversion functions:
toTerm :: Fix (TermF a) -> Term a
toTerm = termF Terminal Application Abstraction
fromTerm :: Term a -> Fix (TermF a)
fromTerm = ana coalg
where coalg (Terminal x) = TerminalF x
coalg (Application x y) = ApplicationF x y
coalg (Abstraction s x) = AbstractionF s x
This means that you can easily define the catamorphism for Term a using the catamorphism for Fix (TermF a). Let's call it foldTerm, as that's probably a little more idiomatic:
foldTerm :: (a -> c) -> (c -> c -> c) -> (String -> c -> c) -> Term a -> c
foldTerm t appl abst = termF t appl abst . fromTerm
Now that you know the type of foldTerm, you can throw away all the TermF machinery and implement it directly.
Direct implementation
Again, you can use typed holes for a simpler, more direct implementation:
foldTerm :: (a -> c) -> (c -> c -> c) -> (String -> c -> c) -> Term a -> c
foldTerm t appl abst (Terminal x) = _
foldTerm t appl abst (Application x y) = _
foldTerm t appl abst (Abstraction s x) = _
The compiler will tell you what you need, and it's fairly clear that the implementation must be something like this:
foldTerm :: (a -> c) -> (c -> c -> c) -> (String -> c -> c) -> Term a -> c
foldTerm t appl abst (Terminal x) = t x
foldTerm t appl abst (Application x y) = appl (recurse x) (recurse y)
where recurse = foldTerm t appl abst
foldTerm t appl abst (Abstraction s x) = abst s (foldTerm t appl abst x)
Keep in mind that the output of foldTerm can be any type c, including Term b: c ~ Term b. Does that enable you do do what you're asking about?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Mark Seemann |
