View Source Hold.Zipper.List (hold v0.1.0)

An implementation of a Zipper over a list

Introductory Article: https://ferd.ca/yet-another-article-on-zippers.html

Supports being able to traverse a list both forwards and backwards while maintaining a focus on the current element much like a doubly-linked list. All while using functional immutable data structures and supporting O(1) lookup and traversal from focus.

So given the list [1, 2, 3, 4, 5] we can imagine being able to "stop" on 3 and then backtrack or keep progressing forward.

    (3)
  2     4
1         5

Zipper.List also supports θ(1) updating the currently focused item.

Summary

Types

t()

left represents items to the left of the focus. right is a list where the head of it is the current focus and the rest is the items to the right of focus.

Functions

Return zipper with focus on the first element.

Return currently focused element.

Return zipper with focus on the last element.

Move focus left, if possible.

Return whether an element to the right exists.

Move focus right, if possible.

Return whether an element to the right exists.

Updates the element which is currently focused.

Types

@type invalid_move() :: {:error, :invalid_move}
@type t() :: {left :: [term()], right :: [term()]}
@type t(a) :: {left :: [a], right :: [a]}

left represents items to the left of the focus. right is a list where the head of it is the current focus and the rest is the items to the right of focus.

Functions

@spec first(t()) :: t()

Return zipper with focus on the first element.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> zipper = Hold.Zipper.List.first(zipper)
iex> Hold.Zipper.List.focus(zipper)
1
@spec focus(t()) :: term()

Return currently focused element.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> Hold.Zipper.List.focus(zipper)
1

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> Hold.Zipper.List.focus(zipper)
2
@spec from_list(list()) :: t()
@spec last(t()) :: t()

Return zipper with focus on the last element.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> zipper = Hold.Zipper.List.last(zipper)
iex> Hold.Zipper.List.focus(zipper)
5
@spec left(t()) :: {:ok, t()} | Hold.Zipper.invalid_move()

Move focus left, if possible.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> {:ok, zipper} = Hold.Zipper.List.left(zipper)
iex> Hold.Zipper.List.focus(zipper)
1

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> Hold.Zipper.List.left(zipper)
{:error, :invalid_move}
@spec left?(t()) :: boolean()

Return whether an element to the right exists.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> Hold.Zipper.List.left?(zipper)
true

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> Hold.Zipper.List.left?(zipper)
false
@spec right(t()) :: {:ok, t()} | Hold.Zipper.invalid_move()

Move focus right, if possible.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> Hold.Zipper.List.focus(zipper)
2

iex> zipper = Hold.Zipper.List.from_list([1])
iex> Hold.Zipper.List.right(zipper)
{:error, :invalid_move}
@spec right?(t()) :: boolean()

Return whether an element to the right exists.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> Hold.Zipper.List.right?(zipper)
true

iex> zipper = Hold.Zipper.List.from_list([1])
iex> Hold.Zipper.List.right?(zipper)
false
@spec to_list(t(term())) :: [term()]
@spec update(t(), new_value :: term()) :: t()

Updates the element which is currently focused.

iex> zipper = Hold.Zipper.List.from_list([1, 2, 3, 4, 5])
iex> {:ok, zipper} = Hold.Zipper.List.right(zipper)
iex> zipper = Hold.Zipper.List.update(zipper, 20)
iex> Hold.Zipper.List.to_list(zipper)
[1, 20, 3, 4, 5]