Pattern matching and tree mutation
In the previous post, I went over how to use zippers along with a tree of records.
Next, I want to pull up a level of abstraction and think about things you might want to do over a tree. We’re working on code these days that does lots of work on trees of heterogeneous records. In particular, much of the work involves building a tree, then (symbolically) manipulating that tree.
It’s useful to thus wrap up the ability to a) match a pattern in the tree and b) mutate the tree when you find a match. Below is a function that takes a zipper and two functions to match and mutate:
(defn tree-edit
"Take a zipper, a function that matches a pattern in the tree,
and a function that edits the current location in the tree. Examine the tree
nodes in depth-first order, determine whether the matcher matches, and if so
apply the editor. On first match, return modified tree."
[zipper matcher editor]
(loop [loc zipper]
(if (z/end? loc)
(z/root loc)
(if-let [matcher-result (matcher loc)]
(let [new-loc (z/edit loc (partial editor matcher-result))]
(if (not (= (z/node new-loc) (z/node loc)))
(z/root new-loc)
(recur (z/next loc))))
(recur (z/next loc))))))
Here “loc” is a zipper location wrapping a point in the tree. The loop/recur walks through the tree using z/next until z/end? is true, then returns z/root. If the matcher matches, the result of the matcher and the current node are given to the editor function used in z/edit. If the returned mutated node is different, then we bail out early.
With this function, we can then write a pair of match and edit functions and apply them:
;; define a rule as a matcher/editor pair
(defn is-scalar? [loc]
(instance? ScalarFunction (z/node loc)))
;; define a tree editor - gets the result of the matcher and the node
(defn eval-scalar [_ {:keys (f exprs)}]
(case f
:+ (apply + exprs)
:- (apply - exprs)
(str "oh noes! -> " f)))
We can then apply this match/edit rule using tree-edit:
(def tree-2 (tree-edit tree-z
is-scalar? eval-scalar))
(def tree-3 (tree-edit (tree-zip tree-2)
is-scalar? eval-scalar))
Our starting tree was tree-z:

which we then applied the rule to get tree-2:

and then applied it again to get tree-3:

It should be pretty obvious that I can write additional rules and build more infrastructure to apply these rules to mutate the tree.
If you found this interesting, I’m also doing a lightning talk today at the Clojure Conj conference and here are the slides:

Hi! My name is Alex Miller and I live in St. Louis. I write code for a living and currently work for
Would’t the most common use case be to apply the editor to *all* matches?
@Martin: Yes! Great question. I didn’t cover that as we’re still playing with the best way to do that. We have some additional functions that apply multiple rules multiple times. The tricky thing with applying the rule more than once in the same pass is that it is quite easy to create an infinite recursion.