Repmin
Replace every leaf of a tree by the tree's minimum, in one traversal. The traversal returns the minimum and the rebuilt tree together, and the rebuilt tree's leaves refer to the minimum the same traversal is still computing. It works because the reference is lazy.
The direct program walks the tree twice, once for the minimum and once to rebuild. Bird's version does both in one pass by feeding the pass's own result back into it:[1]
Bird's program, in Haskell.
data Tree = Leaf Int | Node Tree Treerepmin :: Tree -> Treerepmin t = t'where(m, t') = go tgo (Leaf n) = (n, Leaf m)go (Node l r) = let (a, l') = go l(b, r') = go rin (min a b, Node l' r')
go never inspects m; it only stores it in the new leaves. By the time anything reads a leaf, go has returned and m is known. m depends on the output of go, which depends on m, and the cycle is harmless because nothing on it is demanded before it exists.
In OCaml the knot is tied by hand: the leaves hold a suspension, and a recursive lazy value closes the loop.
type tree = Leaf of int | Node of tree * tree(* The result's leaves hold a suspended minimum, forced only when read. *)type rtree = RLeaf of int Lazy.t | RNode of rtree * rtree(* One pass returns the minimum of a subtree and the rebuilt subtree, whoseleaves all point at m: the minimum of the whole tree, which is onlyknown once this same pass has finished. *)let repmin t =let rec go m = function| Leaf n -> (n, RLeaf m)| Node (l, r) ->let a, l' = go m l inlet b, r' = go m r in(min a b, RNode (l', r'))inlet rec result = lazy (go (lazy (fst (Lazy.force result))) t) insnd (Lazy.force result)let rec show = function| RLeaf m -> string_of_int (Lazy.force m)| RNode (l, r) -> "(" ^ show l ^ " " ^ show r ^ ")"
Running it.
repmin ((5 2) (8 (3 7))) -> ((2 2) (2 (2 2)))
Forcing a leaf before the pass has finished raises Lazy.Undefined, which is how a strict language reports a circular dependency that a lazy one would have sent into a loop.
The same program is an attribute grammar: the minimum is a synthesized attribute, computed bottom-up, and its value at the leaves an inherited one, passed top-down. Circular programs are how a lazy language evaluates such grammars in a single traversal.
see also
- HylomorphismA function that unfolds a seed into a recursive structure and folds that structure into a result, written so the structure is never built: hylo f g = f . fmap (hylo f g) . g. The call tree of the recursion is the intermediate structure.
- ZipperA data structure with a focus: the subterm at the focus, plus the path back to the root together with everything not on it. Moving the focus and editing at it take constant time, and the type of contexts is the derivative of the structure's type.
further reading
- [1]R. S. Bird, “Using circular programs to eliminate multiple traversals of data”, Acta Informatica 21 (1984).
- [2]T. Johnsson, “Attribute grammars as a functional programming paradigm”, FPCA (1987).