tur/zip
generic zippers: a structural zipper over any tree, and an element cursor.
Two persistent zippers, both pure Turmeric:
(ZipLoc T), the STRUCTURAL zipper -- Clojure'sclojure.zip, typed. Describe a homogeneous tree typeTwith three functions (branch?,children,make-node, bundled byzip/ops) and navigate it withzip/down,zip/up,zip/left,zip/right,zip/next,zip/prev; edit it withzip/replace,zip/edit,zip/insert-*,zip/remove; rebuild it withzip/root.zip/rose-zipdoes this for the stock rose tree(Rose A), and aPlatedinstance does it for your own type (zip/plated).(ZipCursor S A), the ELEMENT cursor -- a zipper over theA-elements of any containerS, described by a(Traversal S A). Step through the elements, edit them in place, and close the cursor back into anSof the same shape. Overzip/cons-traversalit is an O(1) list zipper.
Every location is an immutable value: a move or an edit returns a new one and leaves the old one valid. Unedited subtrees are shared rather than copied, and (zip/root (zip/zipper ops t)) returns t itself.
Moves that Clojure answers with nil at a boundary return Option. Edits that Clojure throws on (an insert or remove at the top, the children of a leaf) panic with Clojure's own message.
Memory: locations, frames and rebuilt nodes are :heap values like (Cons A). Outside a region they live until the process exits; inside with-region a session whose result is a scalar rewinds in one step. There is no zip/free.
Not auto-loaded: (load "stdlib/zip.tur"). See docs/guides/zipper-guide.md.
Since: generic-zipper-plan ZP1
ZipOps
(defstruct ZipOps [])
how to navigate a tree type T: the three functions of clojure.zip.
branch? says whether a node can have children, children lists them in order, and make-node rebuilds a node with a new list of children. Build one with zip/ops. :heap :copy, so a location carries it as one word.
Since: generic-zipper-plan ZP1
ZipFrame
(defstruct ZipFrame [])
one level of a location's path (internal).
lefts holds the left siblings NEAREST FIRST, pnode the parent as it was on the way down, up the next frame out (the NULL link at the root), rights the right siblings in order, and changed whether anything below this frame was edited -- zip/up rebuilds the parent only then.
Since: generic-zipper-plan ZP1
ZipLoc
(defstruct ZipLoc [])
a location in a tree of T: the focused node plus the path to it.
end is Clojure's :end marker, set by zip/next past the last node.
Since: generic-zipper-plan ZP1
zip/-top?
(zip/-top? [T])
internal: is this path link the root's (the NULL link)?
zip/-no-frame
(zip/-no-frame [T])
internal: the root's path link.
zip/-rev-onto
(zip/-rev-onto [T])
internal: xs reversed, in front of acc.
zip/-append1
(zip/-append1 [T])
internal: xs with x added at the end.
zip/-loc
(zip/-loc [T])
internal: a location that is not the end marker.
zip/-frame
(zip/-frame [T])
internal: a path frame.
zip/-mark
(zip/-mark [T])
internal: the frame with changed set; the root link as is.
zip/-depth-acc
(zip/-depth-acc [T])
internal: tail-recursive frame count behind zip/depth.
zip/-ancestors-acc
(zip/-ancestors-acc [T])
internal: the parents of every frame from f out, root first.
zip/ops
(zip/ops [T])
bundle the three functions that describe a tree type T.
| branch? | can this node have children? A false answer makes the node | |
a leaf: zip/down gives None and zip/children panics. | ||
| children | the node's children, in order | |
| make-node | the node rebuilt with the given children, in order |
A (ZipOps T) for zip/zipper.
(defdata Tree :heap :copy (Leaf int) (Node (Cons Tree)))
(def tree-ops
(zip/ops (fn [t : Tree] : bool (match t (Leaf _) false (Node _) true))
(fn [t : Tree] : (Cons Tree) (match t (Leaf _) (tnil) (Node cs) cs))
(fn [t : Tree cs : (Cons Tree)] : Tree (Node cs))))
Since: generic-zipper-plan ZP1
zip/zipper
(zip/zipper [T])
open a zipper at the root of a tree.
| ops | how to navigate T (see zip/ops) | |
| root | the tree |
The location of root, with an empty path.
(zip/node (zip/zipper tree-ops t)) ; => t
Since: generic-zipper-plan ZP1
zip/node
(zip/node [T])
the node at a location.
| z | a location |
The focused node.
(zip/node (zip/zipper tree-ops t)) ; => t
Since: generic-zipper-plan ZP1
zip/branch?
(zip/branch? [T])
can the node at a location have children?
| z | a location |
The ops' branch? of the focused node. An empty branch answers true.
(zip/branch? (zip/zipper tree-ops (Leaf 1))) ; => false
Since: generic-zipper-plan ZP1
zip/children
(zip/children [T])
the children of the node at a location, in order.
Panics with "called children on a leaf node" when zip/branch? is false, as Clojure throws: a leaf and an empty branch are different things.
| z | a location whose node is a branch |
The node's children, a (Cons T) -- (tnil) for an empty branch.
(tlength (zip/children (zip/zipper tree-ops t))) ; => number of children
Since: generic-zipper-plan ZP1
zip/make-node
(zip/make-node [T])
build a node with the location's ops.
| z | a location (only its ops are used) | |
| node | the node to rebuild | |
| kids | its new children, in order |
node rebuilt with kids, by the ops' make-node.
(zip/make-node z (zip/node z) (tnil)) ; => the focused node, childless
Since: generic-zipper-plan ZP1
zip/ancestors
(zip/ancestors [T])
the nodes on the path from the root to a location's parent.
Clojure's path. Each is the node as it was on the way down, so an edit below it is not reflected until zip/up rebuilds it.
| z | a location |
The ancestors, root first; (tnil) at the root.
(tlength (zip/ancestors (zip/zipper tree-ops t))) ; => 0
Since: generic-zipper-plan ZP1
zip/lefts
(zip/lefts [T])
the left siblings of a location, in document order.
| z | a location |
The siblings before the node, leftmost first; (tnil) at the root.
(zip/lefts (zip/zipper tree-ops t)) ; => (tnil)
Since: generic-zipper-plan ZP1
zip/rights
(zip/rights [T])
the right siblings of a location, in document order.
| z | a location |
The siblings after the node, nearest first; (tnil) at the root.
(zip/rights (zip/zipper tree-ops t)) ; => (tnil)
Since: generic-zipper-plan ZP1
zip/depth
(zip/depth [T])
how many levels a location is below the root.
| z | a location |
0 at the root, 1 for its children, and so on.
(zip/depth (zip/zipper tree-ops t)) ; => 0
Since: generic-zipper-plan ZP1
zip/end?
(zip/end? [T])
has zip/next walked past the last node?
| z | a location |
true for the end marker zip/next returns after the last node in
pre-order, false otherwise.
(zip/end? (zip/zipper tree-ops t)) ; => false
Since: generic-zipper-plan ZP1
zip/down
(zip/down [T])
move to the leftmost child.
| z | a location |
(Some loc) at the first child; None on a leaf or an empty branch.
(zip/down (zip/zipper tree-ops (Node (tcons-of (Leaf 1) (tnil))))) ; => (Some loc), with (Leaf 1) in focus
Since: generic-zipper-plan ZP1
zip/up
(zip/up [T])
move to the parent, rebuilding it if anything below was edited.
An unedited parent is returned as it was, with no allocation, so a walk that edits nothing shares every node with the original tree.
| z | a location |
(Some loc) at the parent; None at the root.
(zip/up (zip/zipper tree-ops t)) ; => None
Since: generic-zipper-plan ZP1
zip/right
(zip/right [T])
move to the next sibling.
| z | a location |
(Some loc) at the right sibling; None at the rightmost child or the root.
(zip/right (zip/zipper tree-ops t)) ; => None
Since: generic-zipper-plan ZP1
zip/left
(zip/left [T])
move to the previous sibling.
| z | a location |
(Some loc) at the left sibling; None at the leftmost child or the root.
(zip/left (zip/zipper tree-ops t)) ; => None
Since: generic-zipper-plan ZP1
zip/leftmost
(zip/leftmost [T])
move to the first sibling.
| z | a location |
The leftmost sibling's location; z itself when already there or at the root.
(zip/node (zip/leftmost z)) ; => the first child of z's parent
Since: generic-zipper-plan ZP1
zip/rightmost
(zip/rightmost [T])
move to the last sibling.
| z | a location |
The rightmost sibling's location; z itself when already there or at the root.
(zip/node (zip/rightmost z)) ; => the last child of z's parent
Since: generic-zipper-plan ZP1
zip/root
(zip/root [T])
zip all the way up and return the (possibly edited) tree.
| z | a location |
The root node, with every edited level rebuilt. With no edits it is the very tree the zipper was opened on.
(zip/root (zip/replace (zip/zipper tree-ops t) (Leaf 9))) ; => (Leaf 9)
Since: generic-zipper-plan ZP1
zip/-deepest-last
(zip/-deepest-last [T])
internal: the last node in pre-order under z (z if a leaf).
zip/-next-up
(zip/-next-up [T])
internal: next from a node with no child and no right sibling.
zip/next
(zip/next [T])
move to the next node in a depth-first, pre-order walk.
Past the last node it returns the END marker: a location at the (rebuilt) root for which zip/end? is true. zip/next of the end marker is the end marker; zip/root of it is the tree.
| z | a location |
The next location in pre-order, or the end marker.
(defn visit-all [T] [z : (ZipLoc T)] : T (if (zip/end? z) (zip/root z) (visit-all (zip/next z))))
Since: generic-zipper-plan ZP1
zip/prev
(zip/prev [T])
move to the previous node in a depth-first, pre-order walk.
| z | a location |
(Some loc) at the previous node in pre-order; None at the root (and at the end marker).
(zip/prev (zip/zipper tree-ops t)) ; => None
Since: generic-zipper-plan ZP1
zip/replace
(zip/replace [T])
replace the node at a location.
| z | a location | |
| node | the new node |
The same location with node in focus; zip/up and zip/root rebuild
the levels above it. z itself is unchanged.
(zip/node (zip/replace z (Leaf 40))) ; => (Leaf 40)
Since: generic-zipper-plan ZP1
zip/edit
(zip/edit [T])
replace the node at a location with a function of it.
| z | a location | |
| f | the edit, applied to the focused node |
(zip/replace z (f (zip/node z))).
(zip/edit z (fn [t : Tree] : Tree (Leaf 0)))
Since: generic-zipper-plan ZP1
zip/insert-left
(zip/insert-left [T])
insert a sibling immediately to the left, without moving.
Panics with "Insert at top" at the root, as Clojure throws.
| z | a location below the root | |
| item | the new sibling |
The same location, with item as its new left sibling.
(zip/lefts (zip/insert-left z (Leaf 0))) ; => ... ending in (Leaf 0)
Since: generic-zipper-plan ZP1
zip/insert-right
(zip/insert-right [T])
insert a sibling immediately to the right, without moving.
Panics with "Insert at top" at the root, as Clojure throws.
| z | a location below the root | |
| item | the new sibling |
The same location, with item as its new right sibling.
(thead (zip/rights (zip/insert-right z (Leaf 0)))) ; => (Leaf 0)
Since: generic-zipper-plan ZP1
zip/insert-child
(zip/insert-child [T])
add a first child to the node at a location, without moving.
Panics with "called children on a leaf node" when the node is not a branch.
| z | a location whose node is a branch | |
| item | the new leftmost child |
The same location, its node rebuilt with item before its children.
(thead (zip/children (zip/insert-child z (Leaf 0)))) ; => (Leaf 0)
Since: generic-zipper-plan ZP1
zip/append-child
(zip/append-child [T])
add a last child to the node at a location, without moving.
Panics with "called children on a leaf node" when the node is not a branch.
| z | a location whose node is a branch | |
| item | the new rightmost child |
The same location, its node rebuilt with item after its children.
(zip/node (zip/rightmost (unwrap (zip/down (zip/append-child z (Leaf 0)))))) ; => (Leaf 0)
Since: generic-zipper-plan ZP1
zip/remove
(zip/remove [T])
delete the node at a location.
Panics with "Remove at top" at the root, as Clojure throws.
| z | a location below the root |
The location that preceded z in a pre-order walk: the deepest last descendant of its left sibling, or its parent (rebuilt) when z was the first child.
(zip/root (zip/remove z)) ; => the tree without z's node
Since: generic-zipper-plan ZP1
zip/find-next
(zip/find-next [T])
search forward in pre-order for a node satisfying p.
The search starts AT z: z itself is returned when its node satisfies p. To find the following match, search from (zip/next found).
| z | where to start | |
| p | the test, applied to each node |
(Some loc) at the first node, from z on in pre-order, satisfying p; None when the walk reaches the end without one.
(zip/find-next (zip/zipper tree-ops t)
(fn [n : Tree] : bool (match n (Leaf v) (= v 3) (Node _) false)))
; => (Some loc) at (Leaf 3)
Since: generic-zipper-plan ZP2
zip/edit-all
(zip/edit-all [T])
apply f to every node from z on, in pre-order, and return the root.
Clojure's standard edit loop, shipped: edit the node, move with zip/next, stop at the end. Each node is edited BEFORE its children are visited, so the walk descends into what f returned -- the zipper form of Uniplate's transform, top-down. From the root it rewrites the whole tree.
| z | where to start (usually the root location) | |
| f | the edit, applied to each node |
The rebuilt root.
(zip/edit-all (zip/zipper tree-ops t)
(fn [n : Tree] : Tree (match n (Leaf v) (Leaf (* v 2)) (Node _) n)))
; => t with every leaf doubled
Since: generic-zipper-plan ZP2
Rose
(defstruct Rose :heap [A] (label A) (kids (Cons (Rose A))))
a rose tree: a label and a list of child trees (Haskell's Data.Tree).
The typed counterpart of Clojure's seq-zip. Every node is a branch -- a leaf is a node with no children -- so zip/down of a leaf is None and zip/append-child works anywhere. kids is a Cons, so zip/down is O(1). Build one with zip/rose or make-struct.
Since: generic-zipper-plan ZP2
zip/rose
(zip/rose [A])
build a rose tree node.
| label | the node's label | |
| kids | its children, in order ((tnil) for a leaf) |
A (Rose A).
(zip/rose 1 (tcons-of (zip/rose 2 (tnil)) (tnil))) ; => 1 with one child, 2
Since: generic-zipper-plan ZP2
zip/rose-ops
(zip/rose-ops [A])
the ZipOps of a rose tree.
Ops whose branch? is always true, children reads kids, and
make-node keeps the label and replaces the children.
(zip/zipper (zip/rose-ops) r) ; same as (zip/rose-zip r)
Since: generic-zipper-plan ZP2
zip/rose-zip
(zip/rose-zip [A])
open a zipper at the root of a rose tree.
| r | the tree |
(zip/zipper (zip/rose-ops) r).
(.label (zip/node (zip/rose-zip (zip/rose 7 (tnil))))) ; => 7
Since: generic-zipper-plan ZP2
Plated
(defclass Plated [a])
a type whose values have children of the same type.
lens's Plated / Uniplate's Uniplate: the three ZipOps functions as an instance, so zip/plated can open a zipper with no ops argument. An instance gives one canonical view of a type; for a second view of the same type, build a ZipOps with zip/ops instead.
| a | the tree type |
(plate-branch? [x :a] :bool)-
can
xhave children? (plate-children [x :a] :(Cons a))-
x's children, in order (plate-rebuild [x :a kids :(Cons a)] :a)-
xrebuilt withkidsas its children, in order
(definstance Plated [Tree] (plate-branch? [t] (match t (Leaf _) false (Node _) true)) (plate-children [t] (match t (Leaf _) (tnil) (Node cs) cs)) (plate-rebuild [t cs] (Node cs)))
Since: generic-zipper-plan ZP3
Plated[Rose]
(definstance Plated [Rose])
a rose tree's children are its kids; every node is a branch.
zip/plated-ops
(zip/plated-ops [^Plated T])
the ZipOps a Plated instance describes.
Ops that call the instance's plate-branch?, plate-children and plate-rebuild.
(:: (zip/plated-ops) (ZipOps Tree))
Since: generic-zipper-plan ZP3
zip/plated
(zip/plated [^Plated T])
open a zipper on a value of a Plated type.
| root | the tree |
(zip/zipper (zip/plated-ops) root).
(zip/node (zip/plated t)) ; => t
Since: generic-zipper-plan ZP3
Traversal
(defstruct Traversal [])
how to visit the A-elements of a container S, and put them back.
parts lists the elements of an S in order; fill returns the same S with its elements replaced, in order. The record mirrors stdlib/lens.tur's Lens. Laws:
(fill s (parts s)) = s (parts (fill s xs)) = xs, when xs has as many elements as (parts s)
:heap :copy, so a cursor carries it as one word.
Since: generic-zipper-plan ZP4
ZipCursor
(defstruct ZipCursor [])
a cursor over the elements of a container, focused on one.
shape is the container as opened, lefts the elements before the focus (nearest first), rights those after it (in order).
Since: generic-zipper-plan ZP4
zip/traversal
(zip/traversal [S A])
build a Traversal from its two functions.
| parts | the elements of an S, in order | |
| fill | the S with its elements replaced, in order |
A (Traversal S A) for zip/cursor.
(zip/traversal (fn [p : Pair] : (Cons int) ...)
(fn [p : Pair xs : (Cons int)] : Pair ...))
Since: generic-zipper-plan ZP4
zip/cursor
(zip/cursor [S A])
open a cursor on the first element of a container.
Opening reads every element once (O(n)); after that each move and edit is O(1), and every cursor stays valid.
| t | the container's Traversal | |
| s | the container |
(Some cursor) focused on the first element; None when s has none.
(zip/cursor (zip/cons-traversal) (tcons-of 1 (tnil))) ; => (Some c), focus 1
Since: generic-zipper-plan ZP4
zip/cursor-focus
(zip/cursor-focus [S A])
the element a cursor is on.
| c | a cursor |
The focused element.
(zip/cursor-focus c) ; => the current element
Since: generic-zipper-plan ZP4
zip/cursor-set
(zip/cursor-set [S A])
replace the element a cursor is on.
| c | a cursor | |
| a | the new element |
The same position with a in focus; c itself is unchanged.
(zip/cursor-focus (zip/cursor-set c 200)) ; => 200
Since: generic-zipper-plan ZP4
zip/cursor-edit
(zip/cursor-edit [S A])
replace the element a cursor is on with a function of it.
| c | a cursor | |
| f | the edit |
(zip/cursor-set c (f (zip/cursor-focus c))).
(zip/cursor-edit c (fn [x : int] : int (* x 2)))
Since: generic-zipper-plan ZP4
zip/cursor-next
(zip/cursor-next [S A])
move to the next element.
| c | a cursor |
(Some cursor) on the next element; None on the last one.
(zip/cursor-next c) ; => (Some c2), or None at the last element
Since: generic-zipper-plan ZP4
zip/cursor-prev
(zip/cursor-prev [S A])
move to the previous element.
| c | a cursor |
(Some cursor) on the previous element; None on the first one.
(zip/cursor-prev c) ; => (Some c0), or None at the first element
Since: generic-zipper-plan ZP4
zip/cursor-close
(zip/cursor-close [S A])
write the elements back into the container.
| c | a cursor |
The container the cursor was opened on, with every element as the
cursor now has it, by the Traversal's fill (O(n)). The original
container is unchanged.
(zip/cursor-close (zip/cursor-set c 200)) ; => s with that element 200
Since: generic-zipper-plan ZP4
zip/cons-traversal
(zip/cons-traversal [A])
the Traversal of a list: its elements are itself.
A cursor over it is a persistent list zipper with O(1) moves.
A (Traversal (Cons A) A) whose parts is the identity and whose fill
returns the new elements as the list.
(zip/cursor (zip/cons-traversal) xs) ; => a cursor over xs
Since: generic-zipper-plan ZP4
zip/-forest-labels
(zip/-forest-labels [A])
internal: a forest's labels in pre-order, in front of acc.
ZipFill
(defstruct ZipFill :heap [A] (built (Cons (Rose A))) (rest (Cons A)))
internal: a rebuilt forest and the labels it did not use.
zip/-fill-forest
(zip/-fill-forest [A])
internal: relabel a forest in pre-order from xs.
A tree keeps its old label once xs runs out, so fill is total.
zip/rose-labels
(zip/rose-labels [A])
the Traversal of a rose tree's labels, in pre-order.
A (Traversal (Rose A) A): parts lists the labels root first, depth
first; fill relabels the same shape from a list of labels.
(zip/cursor (zip/rose-labels) r) ; => a cursor over r's labels
Since: generic-zipper-plan ZP4