Current section
Files
Jump to
Current section
Files
lib/funcio/enum/map/gather.ex
defmodule Plymio.Funcio.Enum.Map.Gather do
@moduledoc ~S"""
Map and Gather Patterns for Enumerables.
These functions map the elements of an *enum* and gather the
results according to one of the defined *patterns*.
Gathering means collecting all the `:ok` and `:error` results into a
*opts* with keys `:ok` and `:error`. The value of each key will be a list
of 2tuples where the first element is the *enum* element and the
second is either the `value` from {`:ok, value}` or `error` from
`{:error, error}`. If there are no `:ok` or `:error` results, the
key will not be present in the final *opts*.
See `Plymio.Funcio` for overview and documentation terms.
"""
use Plymio.Funcio.Attribute
@type error :: Plymio.Funcio.error()
@type stream :: Plymio.Funcio.stream()
@type opts :: Plymio.Funcio.opts()
import Plymio.Funcio.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Result,
only: [
normalise0_result: 1
],
warn: false
import Plymio.Funcio.Map.Utility,
only: [
reduce_map1_funs: 1
]
@doc ~S"""
`map_gather0_enum/2` take an *enum* and *map/1* and applies
the *map/1* to each element of the *enum* and then gathers the results according to *pattern 0*.
## Examples
iex> fun = fn v -> {:ok, v * v} end
...> [1,2,3] |> map_gather0_enum(fun)
{:ok, [ok: [{1,1},{2,4},{3,9}]]}
iex> fun = fn
...> 3 -> {:error, %ArgumentError{message: "argument is 3"}}
...> v -> {:ok, v + 42}
...> end
...> [1,2,3] |> map_gather0_enum(fun)
{:ok, [ok: [{1,43},{2,44}], error: [{3, %ArgumentError{message: "argument is 3"}}]]}
iex> fun = :not_a_fun
...> {:error, error} = [1,2,3] |> map_gather0_enum(fun)
...> error |> Exception.message
"map/1 function invalid, got: :not_a_fun"
iex> fun = fn v -> {:ok, v} end
...> {:error, error} = 42 |> map_gather0_enum(fun)
...> error |> Exception.message
...> |> String.starts_with?("protocol Enumerable not implemented for 42")
true
"""
@since "0.1.0"
@spec map_gather0_enum(any, any) :: {:ok, opts} | {:error, error}
def map_gather0_enum(enum, fun) do
with {:ok, fun} <- [fun, &normalise0_result/1] |> reduce_map1_funs do
try do
enum
|> Enum.reduce_while({[], []}, fn element, {oks, errors} ->
element
|> fun.()
|> case do
{:error, %{__struct__: _} = error} ->
{:cont, {oks, [{element, error} | errors]}}
{:ok, value} ->
{:cont, {[{element, value} | oks], errors}}
value ->
with {:error, error} <- new_error_result(m: "pattern0 result invalid", v: value) do
{:cont, {oks, [{element, error} | errors]}}
else
{:error, %{__struct__: _}} = result -> {:halt, result}
end
end
end)
|> case do
{:error, %{__exception__: true}} = result -> result
{oks, []} -> {:ok, [ok: oks |> Enum.reverse()]}
{[], errors} -> {:ok, [error: errors |> Enum.reverse()]}
{oks, errors} -> {:ok, [ok: oks |> Enum.reverse(), error: errors |> Enum.reverse()]}
end
rescue
error ->
{:error, error}
end
else
{:error, %{__exception__: true}} = result -> result
end
end
end