ZipperEx behaviour (ZipperEx v0.2.0) View Source

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]}]}
iex> {_zipper, acc} = zipper |> ZipperEx.top() |> ZipperEx.traverse([], fn
...>   %{loc: {_value, _children}} = zipper, acc -> {zipper, acc}
...>   %{loc: value} = zipper, acc -> {zipper, [value | acc]}
...> end)
iex> acc
[0, 6, 5, 4, 2]

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.

Traverses the tree for the given zipper in depth-first pre-order until fun returns {:halt, zipper}. A subtree will be skipped if fun returns {:skip, zipper}.

Traverses the tree for the given zipper in depth-first pre-order until fun returns {:halt, zipper, acc}. A subtree will be skipped if fun returns {:skip, zipper, acc}.

Returns the parent zipper of the given zipper.

Updates the node of the given zipper.

Link to this section Types

Specs

t() :: t(term())

Specs

t(tree) :: %ZipperEx{
  left: [tree],
  loc: tree,
  module: module() | nil,
  path: t(tree) | nil | :end,
  right: [tree]
}

The ZipperEx type:

  • loc: The current location of the zipper.
  • left: The left side.
  • left: The right side.
  • path: The path to the top.
  • module: The zipper module.

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)

View Source

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)

View Source

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

Specs

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

Returns true if the given node is a branch.

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

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

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)

View Source

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)

View Source

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)

View Source

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

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)

View Source

Specs

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

Creates a node from the given node and children.

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

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

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

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

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.

If the zipper is not at the top, just the subtree will be traversed.

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

iex> {1, [{2, [3, 4]}, 5]}
...> |> Support.Zipper.new()
...> |> ZipperEx.down()
...> |> ZipperEx.traverse(fn z ->
...>   ZipperEx.update(z, fn
...>     {value, children} -> {value + 100, children}
...>     value -> value + 200
...>   end)
...> end)
...> |> ZipperEx.root()
{1, [{102, [203, 204]}, 5]}
Link to this function

traverse(zipper, acc, fun)

View Source

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.

If the zipper is not at the top, just the subtree will be traversed.

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

traverse_while(zipper, fun)

View Source

Specs

traverse_while(
  t(),
  (t() -> {:cont, t()} | {:halt, t()} | {:skip, t()})
) :: t()

Traverses the tree for the given zipper in depth-first pre-order until fun returns {:halt, zipper}. A subtree will be skipped if fun returns {:skip, zipper}.

If the zipper is not at the top, just the subtree will be traversed.

Examples

iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse_while(zipper, fn z ->
...>   case ZipperEx.node(z) do
...>     {2, _children} ->
...>       {:halt, z}
...>     _else ->
...>       {:cont, ZipperEx.update(z, fn
...>         {value, children} -> {value + 100, children}
...>         value -> value + 200
...>       end)}
...>   end
...> end)
#ZipperEx<{101, [{2, [3, 4]}, 5]}>

iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse_while(zipper, fn z ->
...>   case ZipperEx.node(z) do
...>     {2, _children} ->
...>       {:skip, z}
...>     _else ->
...>       {:cont, ZipperEx.update(z, fn
...>         {value, children} -> {value + 100, children}
...>         value -> value + 200
...>       end)}
...>   end
...> end)
#ZipperEx<{101, [{2, [3, 4]}, 205]}>
Link to this function

traverse_while(zipper, acc, fun)

View Source

Specs

traverse_while(
  t(),
  acc,
  (t(), acc -> {:cont, t(), acc} | {:halt, t(), acc} | {:skip, t(), acc})
) :: {t(), acc}
when acc: term()

Traverses the tree for the given zipper in depth-first pre-order until fun returns {:halt, zipper, acc}. A subtree will be skipped if fun returns {:skip, zipper, acc}.

If the zipper is not at the top, just the subtree will be traversed.

Examples

iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
...>   case ZipperEx.node(z) do
...>     {5, _children} ->
...>       {:halt, z, acc}
...>     _else ->
...>       updated = ZipperEx.update(z, fn
...>         {value, children} -> {value + 100, children}
...>         value -> value + 200
...>       end)
...>       {:cont, updated, [ZipperEx.node(z) | acc]}
...>   end
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 7]}>
iex> acc
[4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]

iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
...>   case ZipperEx.node(z) do
...>     {5, _children} ->
...>       {:skip, z, acc}
...>     _else ->
...>       updated = ZipperEx.update(z, fn
...>         {value, children} -> {value + 100, children}
...>         value -> value + 200
...>       end)
...>       {:cont, updated, [ZipperEx.node(z) | acc]}
...>   end
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 207]}>
iex> acc
[7, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]

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

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