Packages

plymio_funcio: Function Utilities

Current section

Files

Jump to
plymio_funcio lib funcio enum reduce reduce.ex
Raw

lib/funcio/enum/reduce/reduce.ex

defmodule Plymio.Funcio.Enum.Reduce do
@moduledoc ~S"""
Reduce Functions for Enumerables.
These functions reduce the elements of an *enum* according to one of the defined *patterns*.
See `Plymio.Funcio` for overview and documentation terms.
"""
use Plymio.Funcio.Attribute
@type error :: Plymio.Funcio.error()
import Plymio.Funcio.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Guard,
only: [
is_value_unset_or_nil: 1
]
@doc ~S"""
`reduce0_enum/2` takes an *enum*, the initial accumulator (`initial_s`) and an arity 2 function, and reduces the *enum* according to *pattern 0*.
The arity 2 function is passed the current element from the *enum*
and the accumulator (`s)`.
If the result is `{:ok, s}` the `s` becomes the new accumulator.
If the result is `{:error, error}` or `value` the reduction is
halted, returning `{:error, error}`.
The fianl result is either `{:ok, final_s}` or `{error, error}`.
## Examples
iex> 0 .. 4 |> reduce0_enum(0, fn v,s -> {:ok, s + v} end)
{:ok, 10}
iex> {:error, error} = 0 .. 4 |> reduce0_enum(0, fn v,s -> s + v end)
...> error |> Exception.message
"pattern0 result invalid, got: 0"
iex> {:ok, map} = 0 .. 4
...> |> reduce0_enum(%{}, fn v,s -> {:ok, Map.put(s, v, v * v)} end)
...> map |> Map.to_list |> Enum.sort
[{0, 0}, {1, 1}, {2, 4}, {3, 9}, {4, 16}]
iex> {:error, error} = :not_an_enum
...> |> reduce0_enum(0, fn v,_s -> v end)
...> error |> Exception.message
...> |> String.starts_with?("protocol Enumerable not implemented for :not_an_enum")
true
"""
@since "0.1.0"
@spec reduce0_enum(any, any, any) :: {:ok, any} | {:error, error}
def reduce0_enum(enum, initial_accumulator, fun)
def reduce0_enum(enum, init_acc, fun)
when is_function(fun, 2) do
try do
enum
|> Enum.reduce_while(init_acc, fn v, s ->
fun.(v, s)
|> case do
{:ok, s} -> {:cont, s}
{:error, %{__struct__: _}} = result -> {:halt, result}
v -> {:halt, new_error_result(m: "pattern0 result invalid", v: v)}
end
end)
|> case do
{:error, %{__exception__: true}} = result -> result
s -> {:ok, s}
end
rescue
error ->
{:error, error}
end
end
def reduce0_enum(_enum, _init_acc, fun) do
new_error_result(m: "map/2 function invalid", v: fun)
end
@doc ~S"""
`reduce1_enum/2` takes an *enum*, the initial accumulator
(`initial_s`) and an arity 2 function, and reduces the *enum*
according to *pattern 1*.
The arity 2 function is passed the current element from the *enum*
and the accumulator (`s)`.
If the result is `{:ok, s}` or `s`, the `s` becomes the new accumulator.
If the result is `{:error, error}` the reduction is
halted, returning the `{:error, error}`.
The result is either `{:ok, final_s}` or `{error, error}`.
## Examples
iex> 0 .. 4 |> reduce1_enum(0, fn v,s -> s + v end)
{:ok, 10}
iex> {:ok, map} = 0 .. 4
...> |> reduce1_enum(%{}, fn v,s -> Map.put(s, v, v * v) end)
...> map |> Map.to_list |> Enum.sort
[{0, 0}, {1, 1}, {2, 4}, {3, 9}, {4, 16}]
iex> {:error, error} = :not_an_enum
...> |> reduce1_enum(0, fn v,_s -> v end)
...> error |> Exception.message
...> |> String.starts_with?("protocol Enumerable not implemented for :not_an_enum")
true
"""
@since "0.1.0"
@spec reduce1_enum(any, any, any) :: {:ok, any} | {:error, error}
def reduce1_enum(enum, initial_accumulator, fun)
def reduce1_enum(enum, init_acc, fun)
when is_function(fun, 2) do
try do
enum
|> Enum.reduce_while(init_acc, fn v, s ->
fun.(v, s)
|> case do
{:ok, s} -> {:cont, s}
{:error, %{__struct__: _}} = result -> {:halt, result}
s -> {:cont, s}
end
end)
|> case do
{:error, %{__exception__: true}} = result -> result
s -> {:ok, s}
end
rescue
error ->
{:error, error}
end
end
def reduce1_enum(_enum, _init_acc, fun) do
new_error_result(m: "map/2 function invalid", v: fun)
end
@doc ~S"""
`reduce2_enum/2` takes an *enum*, the initial accumulator
(`initial_s`) and an arity 2 function, and reduces the *enum*
according to *pattern 2*.
The arity 2 function is passed the current element from the *enum*
and the accumulator (`s)`.
If the result is `nil` or [*the unset
value*](https://hexdocs.pm/plymio_fontais/Plymio.Fontais.html#module-the-unset-value),
`s` is unchanged.
If the result is `{:ok, s}` or `s`, the `s` becomes the new
accumulator.
If the result is `{:error, error}` the reduction is halted,
returning the `{:error, error}`.
The result is either `{:ok, final_s}` or `{error, error}`.
## Examples
iex> 0 .. 4 |> reduce2_enum(0, fn v,s -> s + v end)
{:ok, 10}
iex> fun = fn
...> 3, _s -> {:error, %ArgumentError{message: "argument is 3"}}
...> v, s -> {:ok, s + v}
...> end
...> {:error, error} = [1,2,3] |> reduce2_enum(0, fun)
...> error |> Exception.message
"argument is 3"
iex> fun = fn
...> 2, _s -> nil
...> 4, _s -> Plymio.Fontais.the_unset_value
...> v, s -> {:ok, s + v}
...> end
...> [1,2,3,4,5] |> reduce2_enum(0, fun)
{:ok, 9}
iex> {:ok, map} = 0 .. 4
...> |> reduce2_enum(%{}, fn v,s -> Map.put(s, v, v * v) end)
...> map |> Map.to_list |> Enum.sort
[{0, 0}, {1, 1}, {2, 4}, {3, 9}, {4, 16}]
iex> {:error, error} = :not_an_enum
...> |> reduce2_enum(0, fn v,_s -> v end)
...> error |> Exception.message
...> |> String.starts_with?("protocol Enumerable not implemented for :not_an_enum")
true
"""
@since "0.1.0"
@spec reduce2_enum(any, any, any) :: {:ok, any} | {:error, error}
def reduce2_enum(enum, initial_accumulator, fun)
def reduce2_enum(enum, init_acc, fun)
when is_function(fun, 2) do
try do
enum
|> Enum.reduce_while(init_acc, fn v, s ->
fun.(v, s)
|> case do
x when is_value_unset_or_nil(x) -> {:cont, s}
{:ok, s} -> {:cont, s}
{:error, %{__struct__: _}} = result -> {:halt, result}
s -> {:cont, s}
end
end)
|> case do
{:error, %{__exception__: true}} = result -> result
s -> {:ok, s}
end
rescue
error ->
{:error, error}
end
end
def reduce2_enum(_enum, _init_acc, fun) do
new_error_result(m: "map/2 function invalid", v: fun)
end
end