It's fairly easy to build a Traversal'
which visits whatever you like, though this capability is much more general than what Data.Traversable
allows as that Traversal
must visit only and exactly the contained elements.
To begin, let's examine the signature of traverse
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
I like to call the first argument the "injection" function. Anything that is injected into the Applicative
, f
, is "visited" by the Traversal
. In this case, we'd like to visit the MultiNode
s so we can specialize this type already.
multinodes :: Applicative f => (ParseTree a -> f (ParseTree a)) -> ParseTree a -> (f (ParseTree a))
Or
multinodes :: Traversal' (ParseTree a) (ParseTree a)
Now let's consider the definition. Again, the goal is to inject each MultiNode
.
-- No multinodes here, so we use `pure` so as to inject nothing at all
multinodes inj l@Leaf{} = pure l
-- Here's a multinode, so we'll visit it with the injection function
multinodes inj m@Multinode{} = inj m
-- And we need to neither reject (thus stopping the traversal)
-- nor inject a branch. Instead, we continue the traversal deeper
multinodes inj (Node a l r) =
(\l' r' -> Node a l' r') <$> multinodes inj l
<*> multinodes inj r
This is our Traversal'
.
>>> t ^.. multinodes
[MultiNode VP [Node VP (Node VP (Leaf VP) (Leaf PP)) (Node NP (Leaf DP) (Leaf NP)),Node VP (Leaf VP) (Node PP (Leaf PP) (Node NP (Leaf DP) (Leaf NP)))]]
This code isn't much shorter than the code written for findmulti
---in fact, it's nothing more than an unfolded findMulti . traverse
, but it's immediately compatible with other lens
combinators and demonstrates the general method of feeding an Applicative
through a type while targeting the desired inner structures. Writing this Traversal'
just once will be a general tool for almost any kind of visitation upon the MultiNode
s.