defprotocol Iterable do
@moduledoc """
Protocol used to iterate over elements of the iterable. This protocol is used in `Dsv.Any`, `Dsv.All` and `Dsv.None` validators.
The first parameter is the data we want to go through.
The second is the functions that receives one element of the iterable at a time and return one of the value:
* :cont - the iteration should be continue
* :done - the iteration should be halt immediately
There are default implementations for: `List`, `BitString` and `Tuple`.
`BitString` implementation goes through graphemes:
iex> Iterable.iterate("Hello! Dzień dobry!", :cont, fn elem -> if elem == "D", do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate("Hello! Dzień dobry!", :cont, fn elem -> if elem == "e", do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate("Hello! Dzień dobry!", :cont, fn elem -> if elem == "h", do: :halt, else: :cont end)
{:done, :empty}
`List` implementation goes through all elements of the list:
iex> Iterable.iterate(["a", :b, ["c"], %{d: "d"}], :cont, fn elem -> if elem == ["c"], do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate(["a", :b, ["c"], %{d: "d"}], :cont, fn elem -> if elem == %{d: "d"}, do: :halt, else: :cont end)
{:done, :not_empty}
`Tuple` implementation goes throught all elements of the tuple:
iex> Iterable.iterate({"a", :b, ["c"], %{d: "d"}}, :cont, fn elem -> if elem == ["c"], do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate({"a", :b, ["c"], %{d: "d"}}, :cont, fn elem -> if elem == %{d: "d"}, do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate({:a, :b, :c}, :cont, fn elem -> if elem == :d, do: :halt, else: :cont end)
{:done, :empty}
`Map` implementation goes throught all elements of the map:
iex> Iterable.iterate(%{a: :b, c: :d}, :cont, fn {_key, value} -> if value == :d, do: :halt, else: :cont end)
{:done, :not_empty}
iex> Iterable.iterate(%{a: :b, c: :d}, :cont, fn {_key, value} -> if value == :e, do: :halt, else: :cont end)
{:done, :empty}
iex> Iterable.iterate(%{}, :cont, fn {_key, value} -> if value == :e, do: :halt, else: :cont end)
{:done, :empty}
"""
@typedoc """
The value for each step.
"""
@type element :: any()
@typedoc """
It must be a value that is one of the following "tags":
* `:cont` - the iteration should continue
* `:halt` - the iteration should halt immediately
"""
@type tag :: :cont | :halt
@typedoc """
The result value for iterate function.
"""
@type result :: {:done, :empty} | {:done, :not_empty}
@typedoc """
The next function
"""
@type next :: (current_element :: element() -> tag())
@doc """
Go through every element and provide that element to the `t:next/0` function.
"""
@spec iterate(t, tag, next) :: result
def iterate(data, tag, fun)
end
defimpl Iterable, for: List do
def iterate([], :cont, _fun), do: {:done, :empty}
def iterate(_data, :halt, _fun), do: {:done, :not_empty}
def iterate([head | tail], :cont, fun), do: iterate(tail, fun.(head), fun)
end
defimpl Iterable, for: BitString do
def iterate([], :cont, _fun), do: {:done, :empty}
def iterate(_data, :halt, _fun), do: {:done, :not_empty}
def iterate([head | tail], :cont, fun), do: iterate(tail, fun.(head), fun)
def iterate(data, :cont, fun), do: String.graphemes(data) |> iterate(:cont, fun)
end
defimpl Iterable, for: Tuple do
def iterate([], :cont, _fun), do: {:done, :empty}
def iterate(_data, :halt, _fun), do: {:done, :not_empty}
def iterate([head | tail], :cont, fun), do: iterate(tail, fun.(head), fun)
def iterate(data, :cont, fun) do
l = Tuple.to_list(data)
iterate(l, :cont, fun)
end
end
defimpl Iterable, for: Map do
def iterate([], :cont, _fun), do: {:done, :empty}
def iterate(%{} = data, :cont, _fun) when data == %{}, do: {:done, :empty}
def iterate(_data, :halt, _fun), do: {:done, :not_empty}
def iterate([head | tail], :cont, fun), do: iterate(tail, fun.(head), fun)
def iterate(data, :cont, fun) do
l = Map.to_list(data)
iterate(l, :cont, fun)
end
end