xipper v0.1.0 Xipper
An Elixir implementation of Huet’s Zipper, with gratitude to Rich Hickey’s Clojure implementation.
Zippers provide an elegant solution for traversing a tree-like data structure, while maintaining enough state data to reconstruct the entire tree from any of its child nodes.
All that is required to create a zipper for a data structure is the data structure
itself and a set of functions that define behaviours around nodes of the data structure.
See Xipper.new/4
for details.
For the sake of brevity, the documentation for this module’s functions will assume the following code has been run before each example:
iex> zipper = Xipper.new(
...> [1, 2, [3, 4], 5],
...> &is_list/1,
...> fn node -> node end,
...> fn _node, children -> children end
...> )
iex> zipper.focus
[1, 2, [3, 4], 5]
Again, see new/4
for an explanation of the functions passed as arguments here.
Link to this section Summary
Functions
Appends the given child as the right-most child of the current focus, if it is a branch node, without shifting focus
Returns a node’s children if called on a branch node, an error tuple otherwise
Shifts the zipper’s focus down to the leftmost child node of the current focus
Replaces the currently focused node with the result of applying the given function to that node
Returns the current focus of the zipper
Inserts the given child as the left-most child of the current focus, if it is a branch node, without shifting focus
Inserts the given node as the immediate left-hand sibling of the current focus, without shifting focus
Inserts the given node as the immediate right-hand sibling of the current focus, without shifting focus
Returns true if the current focus of the zipper is a branch node, false otherwise
Returns true if the zipper has reached the end of a depth-first walk, false otherwise
Shifts the zipper’s focus to the sibling node directly to the left of the current focus
Moves focus to the leftmost sibling of the current focus
Returns all left-hand siblings of a node
Takes a zipper, a node, and a list of child nodes and returns a new node
constructed from the node and children via the user-defined make_node
function
passed in to Xipper.new/4
Creates a new zipper
Moves to the next node in a depth-first walk through the zipper
Moves to the previous node in a depth-first walk through the zipper
Removes the current focus of the zipper and shifts focus to where the previous node in a depth-first walk would be
Replaces the current focus with the node passed as the second argument
Shifts the zipper’s focus to the sibling node directly to the right of the current focus
Moves focus to the rightmost sibling of the current focus
Returns all right-hand siblings of a node
Traverses a zipper upwards to the root of the zipper
Shifts the zipper’s focus to the current focus’s parent
Link to this section Types
functions() :: [ is_branch: Xipper.is_branch_function(), children: Xipper.children_function(), make_node: Xipper.make_node_function() ]
t() :: %Xipper{ focus: any(), functions: Xipper.functions(), is_end: boolean(), left: [any()], parents: [Xipper.parent()], right: [any()] }
Link to this section Functions
append_child(Xipper.t(), any()) :: Xipper.maybe_zipper()
Appends the given child as the right-most child of the current focus, if it is a branch node, without shifting focus.
This function returns an error if trying to insert a child into a leaf node.
Example
iex> zipper = Xipper.append_child(zipper, [6, 7])
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5, [6, 7]]
iex> zipper |> Xipper.down |> Xipper.append_child(1.5)
{:error, :append_child_of_leaf}
Returns a node’s children if called on a branch node, an error tuple otherwise.
Example
iex> Xipper.children(zipper)
[1, 2, [3, 4], 5]
iex> zipper |> Xipper.down |> Xipper.children
{:error, :children_of_leaf}
Shifts the zipper’s focus down to the leftmost child node of the current focus.
This function returns an error tuple if the current focus is a leaf node, or a branch node with no children.
Example
iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> Xipper.down(zipper)
{:error, :down_from_leaf}
Replaces the currently focused node with the result of applying the given function to that node.
Example
iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.edit(zipper, &to_string/1)
iex> Xipper.focus(zipper)
"1"
Returns the current focus of the zipper.
Example
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper |> Xipper.down |> Xipper.focus
1
insert_child(Xipper.t(), any()) :: Xipper.maybe_zipper()
Inserts the given child as the left-most child of the current focus, if it is a branch node, without shifting focus.
This function returns an error if trying to insert a child into a leaf node.
Example
iex> zipper = Xipper.insert_child(zipper, 0)
iex> Xipper.focus(zipper)
[0, 1, 2, [3, 4], 5]
iex> zipper |> Xipper.down |> Xipper.insert_child(0)
{:error, :insert_child_of_leaf}
insert_left(Xipper.t(), any()) :: Xipper.maybe_zipper()
Inserts the given node as the immediate left-hand sibling of the current focus, without shifting focus.
This function returns an error tuple if called on the root of a zipper.
Example
iex> zipper = Xipper.down(zipper)
iex> zipper = Xipper.insert_left(zipper, -10)
iex> zipper |> Xipper.root |> Xipper.focus
[-10, 1, 2, [3, 4], 5]
insert_right(Xipper.t(), any()) :: Xipper.maybe_zipper()
Inserts the given node as the immediate right-hand sibling of the current focus, without shifting focus.
This function returns an error tuple if called on the root of a zipper.
Example
iex> zipper = Xipper.down(zipper)
iex> zipper = Xipper.insert_right(zipper, 1.5)
iex> zipper |> Xipper.root |> Xipper.focus
[1, 1.5, 2, [3, 4], 5]
Returns true if the current focus of the zipper is a branch node, false otherwise.
Example
iex> Xipper.is_branch(zipper)
true
iex> zipper |> Xipper.down |> Xipper.is_branch
false
Returns true if the zipper has reached the end of a depth-first walk, false otherwise.
Example
iex> Xipper.is_end(zipper)
false
iex> zipper |> Xipper.down |> Xipper.rightmost |> Xipper.next |> Xipper.is_end
true
Shifts the zipper’s focus to the sibling node directly to the left of the current focus.
This function returns an error tuple if the current focus is the leftmost of its siblings.
Example
iex> zipper = Xipper.down(zipper)
iex> zipper |> Xipper.left
{:error, :left_of_leftmost}
iex> zipper = zipper |> Xipper.rightmost |> Xipper.left
iex> Xipper.focus(zipper)
[3, 4]
Moves focus to the leftmost sibling of the current focus.
iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.right
iex> Xipper.focus(zipper)
[3 ,4]
iex> zipper = Xipper.leftmost(zipper)
iex> Xipper.focus(zipper)
1
Returns all left-hand siblings of a node.
Example
iex> zipper |> Xipper.down |> Xipper.lefts
[]
iex> zipper |> Xipper.down |> Xipper.rightmost |> Xipper.lefts
[1, 2, [3, 4]]
Takes a zipper, a node, and a list of child nodes and returns a new node
constructed from the node and children via the user-defined make_node
function
passed in to Xipper.new/4
.
In the case of our example zipper, since a list node’s children are simply the list itself, in this context this function will return the list of children passed as the third argument.
Example
iex> Xipper.make_node(zipper, [1,2,3], [4,5,6])
[4, 5, 6]
new( any(), Xipper.is_branch_function(), Xipper.children_function(), Xipper.make_node_function() ) :: Xipper.t()
Creates a new zipper.
Creating a zipper requires four arguments. The first argument is the data structure to be traversed by the zipper, and the final three arguments are functions. In order, these functions are:
- a function that takes a node from the data structure and returns true if it is a branch node (that is, it has children or can have children), and false otherwise
- a function that takes a node from the data structure and returns its children if it is a branch node
- a function that takes a node and a list of children and returns a new node with those children
As an example, the following code returns a zipper for a nested list.
Example
iex> zipper = Xipper.new(
...> [1, 2, [3, 4], 5],
...> &is_list/1,
...> fn node -> node end,
...> fn _node, children -> children end
...> )
iex> zipper.focus
[1, 2, [3, 4], 5]
The given arguments are
- the root list
- a function for defining a branch node — in this case whether a node is a list
- a function for returing a node’s children — since a branch node is simply a list, this returns the node itself
- a function for creating a new node — since a branch is just a list of its children, this function returns the list of children as the new node
Moves to the next node in a depth-first walk through the zipper.
next/1
will attempt to move down/1
, then right/1
, and then seek back up
the zipper until it finds a right-hand sibling to move to. Once it reaches the
end of the walk it will return the root of the zipper indefinitely.
Example
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
2
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
[3, 4]
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
4
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
5
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
Moves to the previous node in a depth-first walk through the zipper.
prev/1
will attempt to move left/1
, then recuse down through its left siblings
children, then up/1
, until it reaches the root of the zipper. Calling
prev/1
on a zipper that has reached the end of its depth-first walk
(for which is_end/1
returns true), will return the same zipper indefinitely.
Example
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = zipper |> Xipper.down |> Xipper.rightmost
iex> Xipper.focus(zipper)
5
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
4
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[3, 4]
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
2
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = zipper |> Xipper.down |> Xipper.rightmost |> Xipper.next
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
Removes the current focus of the zipper and shifts focus to where the previous node in a depth-first walk would be.
This function returns an error if trying to remove the root of the zipper.
Example
iex> Xipper.remove(zipper)
{:error, :remove_of_root}
iex> zipper = zipper |> Xipper.down |> Xipper.remove
iex> Xipper.focus(zipper)
[2, [3, 4], 5]
iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.remove
iex> Xipper.focus(zipper)
1
iex> Xipper.rights(zipper)
[[3, 4], 5]
Replaces the current focus with the node passed as the second argument.
Example
iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.replace(zipper, 42)
iex> Xipper.focus(zipper)
42
Shifts the zipper’s focus to the sibling node directly to the right of the current focus.
This function returns an error tuple if the current focus is the rightmost of its siblings.
Example
iex> zipper = Xipper.down(zipper)
iex> zipper |> Xipper.right |> Xipper.focus
2
iex> zipper = Xipper.rightmost(zipper)
iex> Xipper.focus(zipper)
5
iex> Xipper.right(zipper)
{:error, :right_of_rightmost}
Moves focus to the rightmost sibling of the current focus.
iex> zipper = zipper |> Xipper.down |> Xipper.rightmost
iex> Xipper.focus(zipper)
5
Returns all right-hand siblings of a node.
Example
iex> zipper |> Xipper.down |> Xipper.rights
[2, [3, 4], 5]
Traverses a zipper upwards to the root of the zipper.
This function has no effect if the current focus is already the root.
Example
iex> zipper = Xipper.root(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.right |> Xipper.down
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.root(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
Shifts the zipper’s focus to the current focus’s parent.
This function returns an error if the current focus is the root of the zipper.
Example
iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.up(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> Xipper.up(zipper)
{:error, :up_from_root}