ZipperEx behaviour (ZipperEx v0.1.0)
An Elixir implementation for Zipper based on the paper Functional pearl: the zipper. by Gérard Huet.
ZipperEx
provides functions to handle an traverse tree data structures.
Usage
To create a zipper you can use the ZipperEx.Zipable
protocol or create a
module.
Creating a zipper module
Imagine we have a tree structure based on a tuple with an integer value and a list of cildren. The leafs of the tree are also integers. A tree may then look like the following:
{1, [2, {3, [4, 5]}, 6, {7, [0]}]}
# 1
# +-+-+---+-+
# 2 3 6 7
# +-+-+ +
# 4 5 0
To handle this tree we create the following module:
defmodule Support.Zipper do
use ZipperEx
@impl ZipperEx
def branch?(item) do
case item do
{_, [_ | _]} -> true
_else -> false
end
end
@impl ZipperEx
def children({_, children}), do: children
@impl ZipperEx
def make_node({value, _children}, children), do: {value, children}
def make_node(value, children), do: {value, children}
end
For use ZipperEx
we have to implement the callbacks branch?/1
,
children/1
and make_node/2
.
The Support.Zipper
module contains all functions the ZipperEx
also
provides.
We can now use Support.Zipper
.
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [2, {3, [4, 5]}, 6, {7, [0]}]})
#ZipperEx<{1, [2, {3, [4, 5]}, 6, {7, [0]}]}>
iex> zipper = Zipper.down(zipper)
#ZipperEx<2>
iex> zipper = Zipper.right(zipper)
#ZipperEx<{3, [4, 5]}>
iex> Zipper.remove(zipper) |> Zipper.root()
{1, [2, 6, {7, [0]}]}
Using the ZipperEx.Zipable
protocol
Similar to the module example the protocol needs implementations for
ZipperEx.Zipable.branch?/1
, ZipperEx.Zipable.children/1
and
ZipperEx.Zipable.make_node/2
.
defmodule Support.TreeNode do
@moduledoc false
defstruct value: nil, children: []
def new({value, children}) do
struct!(__MODULE__, value: value, children: Enum.map(children, &new/1))
end
def new(value), do: struct!(__MODULE__, value: value)
defimpl ZipperEx.Zipable do
def branch?(%{children: [_ | _]}), do: true
def branch?(_node), do: false
def children(%{children: children}), do: children
def make_node(node, children), do: %{node | children: children}
end
defimpl Inspect do
def inspect(node, _opts), do: "#TreeNode<#{node.value}, #{inspect(node.children)}>"
end
end
Support.TreeNode
can then be used as follows:
iex> alias Support.TreeNode
iex> tree = TreeNode.new({1, [2, {3, [4, 5]}]})
iex> zipper = ZipperEx.new(tree)
iex> ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> zipper |> ZipperEx.down() |> ZipperEx.rightmost()
#ZipperEx<#TreeNode<3, [#TreeNode<4, []>, #TreeNode<5, []>]>>
iex> {_zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
...> node = ZipperEx.node(z)
...> {z, [node.value | acc]}
...> end)
iex> acc
[5, 4, 3, 2, 1]
Example supporters
The module Support.Zipper
and Support.TreeNode
are also used in the
examples for this module.
Link to this section Summary
Callbacks
Should return true
if the given node
is a branch.
Returns the children of the given node
.
Creates a node
from the given node
and children
.
Functions
Appends a child
to the given zipper
.
Returns true
if the given node
is a branch.
Returns the children of the given node
.
Returns the leftmost child node of the given zipper
.
Returns true
when the zipper has reached the end.
Returns the first zipper for which fun
returns a truthy value. If no such
zipper is found, returns nil.
Inserts a child
to the given zipper
at leftmost position.
Inserts a child
as a left sibling to the given zipper.
Inserts a child
as a right sibling to the given zipper.
Returns the left sibling of the given zipper
.
Returns the leftmost sibling of the given zipper
.
Creates a node
from the given node
and children
.
Returns a zipper where each node is the result of invoking fun
on each
corresponding node of the zipper.
Returns a new %ZipperEx{}
from a given tree
or %ZipperEx
.
Returns the next zipper for the given zipper
.
Returns the node from the given zipper
.
Returns the previours zipper for the given zipper
.
Removes the given zipper
.
Replaces the zipper
with a zipper
with the given node
.
Returns the right sibling of the given zipper
.
Returns the rightmost sibling of the given zipper
.
Returns the root node.
Returns the top level zipper.
Traverses the tree for the given zipper
in depth-first pre-order and invokes
fun
for each zipper along the way.
Traverses the tree for the given zipper
in depth-first pre-order and invokes
fun
for each zipper along the way with the accumulator.
Returns the parent zipper of the given zipper
.
Updates the node of the given zipper
.
Link to this section Types
Link to this section Callbacks
branch?(node)
Specs
Should return true
if the given node
is a branch.
children(node)
Specs
Returns the children of the given node
.
make_node(node, children)
Specs
Creates a node
from the given node
and children
.
Link to this section Functions
append_child(zipper, child)
Specs
Appends a child
to the given zipper
.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new(1)
#ZipperEx<1>
iex> zipper = Zipper.append_child(zipper, 2)
#ZipperEx<{1, [2]}>
iex> Zipper.append_child(zipper, 3)
#ZipperEx<{1, [2, 3]}>
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
iex> ZipperEx.append_child(zipper, TreeNode.new(3))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
branch?(zipper)
Specs
Returns true
if the given node
is a branch.
children(zipper)
Specs
Returns the children of the given node
.
down(zipper)
Specs
Returns the leftmost child node of the given zipper
.
Returns nil
if the zipper
is not a branch.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> ZipperEx.down(zipper)
nil
end?(zipper_ex)
Specs
Returns true
when the zipper has reached the end.
A zipper reached his end by using traverse/2
, traverse/3
or calling
next/1
until the end.
Examples
iex> zipper = Support.Zipper.new({1, [2]})
iex> ZipperEx.end?(zipper)
false
iex> zipper |> ZipperEx.traverse(&Function.identity/1) |> ZipperEx.end?()
true
iex> zipper |> ZipperEx.next() |> ZipperEx.next() |> ZipperEx.end?()
true
find(zipper, fun)
Specs
Returns the first zipper for which fun
returns a truthy value. If no such
zipper is found, returns nil.
Runs through the tree in a depth-first pre-order.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> ZipperEx.find(zipper, fn node -> ZipperEx.branch?(node) == false end)
#ZipperEx<#TreeNode<2, []>>
insert_child(zipper, child)
Specs
Inserts a child
to the given zipper
at leftmost position.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [3]}))
iex> ZipperEx.insert_child(zipper, TreeNode.new(2))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
insert_left(zipper, child)
Specs
Inserts a child
as a left sibling to the given zipper.
Raises an ArgumentError
when called with an top level zipper.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [3]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper |> ZipperEx.insert_left(TreeNode.new(2)) |> ZipperEx.top()
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
insert_right(zipper, child)
Specs
Inserts a child
as a right sibling to the given zipper.
Raises an ArgumentError
when called with an top level zipper.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [2]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper |> ZipperEx.insert_right(TreeNode.new(3)) |> ZipperEx.top()
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
left(zipper)
Specs
Returns the left sibling of the given zipper
.
Returns nil
if the zipper
doesn't have a left sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.right(zipper)
#ZipperEx<#TreeNode<11, []>>
iex> ZipperEx.right(zipper)
nil
iex> zipper = ZipperEx.left(zipper)
#ZipperEx<#TreeNode<10, []>>
iex> ZipperEx.left(zipper)
nil
leftmost(zipper)
Specs
Returns the leftmost sibling of the given zipper
.
Returns the zipper
himself if the given zipper
is the leftmost sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11, 12]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
iex> ZipperEx.leftmost(zipper)
#ZipperEx<#TreeNode<10, []>>
make_node(zipper, children)
Specs
Creates a node
from the given node
and children
.
map(zipper, fun)
Specs
Returns a zipper where each node is the result of invoking fun
on each
corresponding node of the zipper.
Runs through the tree in depth-first per-order.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [2, {3, [400, 500]}, 6]})
iex> Zipper.map(zipper, fn
...> {value, children} -> {value * 2, children}
...> value -> value * 2
...> end)
#ZipperEx<{2, [4, {6, [800, 1000]}, 12]}>
new(zipper)
Specs
Returns a new %ZipperEx{}
from a given tree
or %ZipperEx
.
Examples
iex> alias Support.Zipper
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper |> ZipperEx.next() |> ZipperEx.new()
#ZipperEx<{2, [3, 4]}>
iex> zipper |> Zipper.next() |> ZipperEx.new()
#ZipperEx<{2, [3, 4]}>
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>]>>
iex> zipper |> ZipperEx.next() |> ZipperEx.new()
#ZipperEx<#TreeNode<2, []>>
next(zipper)
Specs
Returns the next zipper for the given zipper
.
The function walks through the tree in a depth-first pre-order. After the last
Returns to the root after the last zipper and marks the zipper as ended. An
ended zipper is detectable via end?/1
. Calling next/1
for
an ended zipper
returns the given zipper
.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<3>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<4>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<5>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.next(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.end?(zipper)
true
node(zipper_ex)
Specs
Returns the node from the given zipper
.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.node(zipper)
{1, [{2, [3, 4]}, 5]}
prev(zipper)
Specs
Returns the previours zipper for the given zipper
.
The function walks through the tree in the opposit direction as next/1
.
If no previous zipper is availble, nil
will be returned.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<3>
iex> zipper = ZipperEx.prev(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.prev(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.prev(zipper)
nil
remove(zipper)
Specs
Removes the given zipper
.
The function returns the previous zipper
. If remove/1
is called with an
top level zipper an ArgumentError
will be raised.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<{2, [3, 4]}>
iex> ZipperEx.remove(zipper)
#ZipperEx<{1, [5]}>
replace(zipper, node)
Specs
Replaces the zipper
with a zipper
with the given node
.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper |> ZipperEx.down() |> ZipperEx.replace(7) |> ZipperEx.root()
{1, [7, 5]}
right(zipper)
Specs
Returns the right sibling of the given zipper
.
Returns nil
if the zipper
doesn't have a right sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.right(zipper)
#ZipperEx<#TreeNode<11, []>>
iex> ZipperEx.right(zipper)
nil
iex> zipper = ZipperEx.left(zipper)
#ZipperEx<#TreeNode<10, []>>
iex> ZipperEx.left(zipper)
nil
rightmost(zipper)
Specs
Returns the rightmost sibling of the given zipper
.
Returns the zipper
himself if the given zipper
is the rightmost sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11, 12]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
iex> ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
root(zipper)
Specs
Returns the root node.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = zipper |> Zipper.down() |> Zipper.down()
#ZipperEx<3>
iex> Zipper.root(zipper)
{1, [{2, [3, 4]}, 5]}
top(zipper)
Specs
Returns the top level zipper.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = zipper |> Zipper.down() |> Zipper.down()
#ZipperEx<3>
iex> Zipper.top(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
traverse(zipper, fun)
Specs
Traverses the tree for the given zipper
in depth-first pre-order and invokes
fun
for each zipper along the way.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse(zipper, fn z ->
...> ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> end)
#ZipperEx<{101, [{102, [203, 204]}, 205]}>
traverse(zipper, acc, fun)
Specs
Traverses the tree for the given zipper
in depth-first pre-order and invokes
fun
for each zipper along the way with the accumulator.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> {zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
...> updated = ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> {updated, [ZipperEx.node(z) | acc]}
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, 205]}>
iex> acc
[5, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, 5]}]
up(zipper_ex)
Specs
Returns the parent zipper of the given zipper
.
Returns nil
if the zipper
is the root.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> ZipperEx.up(zipper)
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
update(zipper, fun)
Specs
Updates the node of the given zipper
.
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
iex> zipper = ZipperEx.update(zipper, fn %TreeNode{} = node ->
...> %{node | value: 99}
...> end)
#ZipperEx<#TreeNode<99, []>>
iex> ZipperEx.root(zipper)
#TreeNode<1, [#TreeNode<99, []>, #TreeNode<3, []>]>