An Elixir implementation of Huet's Zipper, with gratitude to Rich Hickey's Clojure implementation.
@deps [
ex_zipper: "~> 0.1.3"
]Below is an outline of the API provided by ExZipper.Zipper. See the full
documentation here for usage examples
ExZipper provides one zipper construction function for working with plain lists,
ExZipper.Zipper.list_zipper/1
iex> Zipper.list_zipper([1,[],2,[3,[4,5]]])
For more complex cases, ExZipper.Zipper.zipper/4 provides a mechanism to
define your own zipper, passing for its first three arguments functions to
- determine whether a node is a branch
- return the children of a branch node
- create a new node from an existing node and a new set of children
and your root data structure as its fourth argument.
For example, to create a zipper for a data structure of nested maps,
where a branch's children are defined under the :kids key:
iex> Zipper.zipper(
...> fn node -> is_map(node) and Map.has_key?(node, :kids) end,
...> fn %{kids: kids} -> kids end,
...> fn node, new_kids -> %{node | kids: new_kids} end,
...> %{ value: "top", kids: [%{value: "first inner"}, %{value: "last inner}]}
...> )
The following functions move through a zipper and return either a new zipper,
or an error of the form {:error, :atom_describing_the_error}
Zipper.downmoves to the first (leftmost) child of the current nodeZipper.upmoves to the parent of the current nodeZipper.rightmoves to the next sibling on the right of the current nodeZipper.leftmoves to the next sibling on the left of the current nodeZipper.rightmostmoves to the furthest right sibling of the current node, or remains in place if already focused on the rightmost siblingZipper.leftmostmoves to the furthest left sibling of the current node, or remains in place if already focused on the leftmost siblingZipper.roottraverses the zipper all the way back to the root node, or remains in place if already focused on the root
The following functions move through a zipper via a depth-first walk. That is, moving as deep down one branch of the tree before moving laterally.
Zipper.nextmoves to the next node, preferring to move to a child over a siblingZipper.prevmoves back up through a depth-first ordering of the treeZipper.end?returnstrueif the zipper has reached the end of a depth-first walk. At this point, the walk has been exhausted, and cannot be reversed.
The following functions modify the zipper without shifting focus away from the current node. They will return a zipper, or an error, if called with an invalid zipper as input (for example, trying to insert a child into a leaf node)
Zipper.insert_childinserts a new child as the first (leftmost) child of the current node, without moving focus from the current nodeZipper.append_childinserts a new child as the last (rightmost) child of the current node, without moving focus from the current nodeZipper.insert_leftinserts a new node as the sibling immediately to the left of the current node, without moving focus from the current nodeZipper.insert_rightinserts a new node as the sibling immediately to the right of the current node, without moving focus from the current nodeZipper.replace/2replaces the zipper's current node with the new node passed to this function as the second argumentZipper.edit/2replaces the zipper's current node with the result of calling the function passed as a second argument on the current nodeZipper.remove/1removes the current node from the zipper, returning a zipper focused on the previous node (via a depth-first walk)
-
Zipper.node/1returns the zipper's current node -
Zipper.lefts/1returns a list of the current node's left siblings -
Zipper.rights/1returns a list of the current node's right siblings -
Zipper.path/1returns a list of nodes creating a path from the root down to, but excluding, the current node -
Zipper.to_list/1returns a depth-first walk ordered list of a zipper's elements -
Zipper.branch?returns true if the current node is a branch (that is, if it can have children, even if it currently does not have any) -
Zipper.childrenreturns a list of the current node's children, or an error if called on a leaf -
Zipper.make_nodetakes a zipper, a node, and a list of children and returns a new node constructed from the second and third arguments. The first argument, the zipper, is passed only to provide context on how to construct the new node.
This library is released under the UNLICENSE.