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

Specs

t() :: t(term())
Link to this opaque

t(tree)

(opaque)

Specs

t(tree)

Specs

tree() :: term()

Link to this section Callbacks

Specs

branch?(node :: term()) :: boolean()

Should return true if the given node is a branch.

Specs

children(node :: term()) :: [term()]

Returns the children of the given node.

Link to this callback

make_node(node, children)

Specs

make_node(node :: term(), children :: [term()]) :: term()

Creates a node from the given node and children.

Link to this section Functions

Link to this function

append_child(zipper, child)

Specs

append_child(t(), tree()) :: t()

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, []>]>>
Link to this function

branch?(zipper)

Specs

branch?(t()) :: boolean()

Returns true if the given node is a branch.

Link to this function

children(zipper)

Specs

children(t()) :: [tree()]

Returns the children of the given node.

Specs

down(t()) :: t()

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
Link to this function

end?(zipper_ex)

Specs

end?(t()) :: boolean()

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
Link to this function

find(zipper, fun)

Specs

find(t() | nil, function()) :: t() | nil

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, []>>
Link to this function

insert_child(zipper, child)

Specs

insert_child(t(), tree()) :: t()

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, []>]>>
Link to this function

insert_left(zipper, child)

Specs

insert_left(t(), tree()) :: t()

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, []>]>>
Link to this function

insert_right(zipper, child)

Specs

insert_right(t(), tree()) :: t()

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, []>]>>

Specs

left(t()) :: t() | nil

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
Link to this function

leftmost(zipper)

Specs

leftmost(t()) :: t()

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, []>>
Link to this function

make_node(zipper, children)

Specs

make_node(t(), [tree()]) :: t()

Creates a node from the given node and children.

Link to this function

map(zipper, fun)

Specs

map(t(), (tree() -> tree())) :: t()

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]}>

Specs

new(t() | tree()) :: t()

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, []>>

Specs

next(t()) :: t()

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
Link to this function

node(zipper_ex)

Specs

node(t()) :: tree()

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]}

Specs

prev(t()) :: t()

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

Specs

remove(t()) :: t()

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]}>
Link to this function

replace(zipper, node)

Specs

replace(t(), tree()) :: t()

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]}

Specs

right(t()) :: t() | nil

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
Link to this function

rightmost(zipper)

Specs

rightmost(t()) :: t()

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, []>>

Specs

root(t()) :: tree()

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]}

Specs

top(t()) :: t()

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]}>
Link to this function

traverse(zipper, fun)

Specs

traverse(t(), (t() -> t())) :: t()

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]}>
Link to this function

traverse(zipper, acc, fun)

Specs

traverse(
  t(),
  acc,
  (t(), acc -> {t(), acc})
) :: {t(), acc}
when acc: term()

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]}]

Specs

up(t()) :: t() | nil

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, []>]>>
Link to this function

update(zipper, fun)

Specs

update(t(), (tree() -> tree())) :: t()

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, []>]>