Starting with enumerable helpers.
This commit is contained in:
parent
7dec0917f7
commit
251106ad34
3 changed files with 158 additions and 0 deletions
|
@ -1,8 +1,12 @@
|
|||
defmodule Upshot do
|
||||
alias Upshot.{Option, Result}
|
||||
|
||||
@moduledoc """
|
||||
Upshot is a library for working with result types; either your own or
|
||||
conventional ok/error tuples.
|
||||
|
||||
Currently experimental.
|
||||
"""
|
||||
|
||||
@type t :: Option.t() | Result.t()
|
||||
end
|
||||
|
|
147
lib/upshot/result/enum.ex
Normal file
147
lib/upshot/result/enum.ex
Normal file
|
@ -0,0 +1,147 @@
|
|||
defmodule Upshot.Result.Enum do
|
||||
alias Upshot.{Result, Result.Proto}
|
||||
|
||||
@moduledoc """
|
||||
Replacements or extensions for the standard library `Enum` functions which use
|
||||
`Result`.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Removes any elements which contain ok results from the enumerable.
|
||||
|
||||
## Note
|
||||
|
||||
This function tolerates values which are not result types and does not remove
|
||||
them from the resulting enumerable. Use `reject_oks!` if you want to ensure
|
||||
there are no non-results in the collection.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [Result.ok(1), Result.error(2), Result.ok(3)]
|
||||
...> |> reject_oks()
|
||||
[{:error, 2}]
|
||||
"""
|
||||
@spec reject_oks(Enum.t()) :: Enum.t()
|
||||
def reject_oks(enumerable),
|
||||
do: Enum.reject(enumerable, &(Proto.valid?(&1) && Proto.ok?(&1)))
|
||||
|
||||
@doc """
|
||||
Removes any elements which contain ok results from the enumerable, returning
|
||||
only error results.
|
||||
|
||||
## Note
|
||||
|
||||
This function raises an `ArgumentError` if it encounters an element which is
|
||||
not a result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [Result.ok(1), Result.error(2), Result.ok(3)]
|
||||
...> |> reject_oks!()
|
||||
[{:error, 2}]
|
||||
|
||||
iex> [Result.ok(1), {}]
|
||||
...> |> reject_oks!()
|
||||
** (ArgumentError) Expected result, but got {}
|
||||
"""
|
||||
@spec reject_oks!(Enum.t()) :: Enum.t() | no_return
|
||||
def reject_oks!(enumerable), do: Enum.reject(enumerable, &Result.ok?(&1))
|
||||
|
||||
@doc """
|
||||
Removes any elements which contain error results from the enumerable.
|
||||
|
||||
## Note
|
||||
|
||||
This function tolerates values which are not result types and does not remove
|
||||
them from the resulting enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [Result.ok(1), Result.error(2), Result.ok(3)]
|
||||
...> |> reject_errors()
|
||||
[{:ok, 1}, {:ok, 3}]
|
||||
"""
|
||||
@spec reject_errors(Enum.t()) :: Enum.t()
|
||||
def reject_errors(enumerable),
|
||||
do: Enum.reject(enumerable, &(Proto.valid?(&1) && Proto.error?(&1)))
|
||||
|
||||
@doc """
|
||||
Removes any elements which contain error results from the enumerable.
|
||||
|
||||
## Note
|
||||
|
||||
This function raises an `ArgumentError` if it encounters an element which is
|
||||
not a result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> [Result.ok(1), Result.error(2), Result.ok(3)]
|
||||
...> |> reject_errors!()
|
||||
[{:ok, 1}, {:ok, 3}]
|
||||
|
||||
iex> [Result.ok(1), {}]
|
||||
...> |> reject_errors!()
|
||||
** (ArgumentError) Expected result, but got {}
|
||||
"""
|
||||
@spec reject_errors!(Enum.t()) :: Enum.t()
|
||||
def reject_errors!(enumerable),
|
||||
do: Enum.reject(enumerable, &Result.error?(&1))
|
||||
|
||||
@doc """
|
||||
Invokes `fun` for each element in `enumerable` with the accumulator.
|
||||
|
||||
Uses the first element of `enumerable` as the initial accumulator value.
|
||||
Reduces as long as `fun` returns an ok result.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reduce_while_ok(1..5, &({:ok, &1 + &2}))
|
||||
16
|
||||
|
||||
iex> reduce_while_ok(1..5, &(if &1 < 4, do: {:ok, &1 + &2}, else: {:error, &1}))
|
||||
{:error, 4}
|
||||
"""
|
||||
@spec reduce_while_ok(Enum.t(), (any, any -> Result.t())) :: Result.t()
|
||||
def reduce_while_ok(enumerable, fun) when is_function(fun) do
|
||||
case Enum.take(enumerable, 1) do
|
||||
[first] -> reduce_while_ok(enumerable, first, fun)
|
||||
[] -> raise Enum.EmptyError, "empty error"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invokes `fun` for each element in `enumerable` with the accumulator starting
|
||||
from `initial_value`.
|
||||
|
||||
Reduces as long as `fun` returns an ok result.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
iex> reduce_while_ok(1..5, 9, &({:ok, &1 + &2}))
|
||||
24
|
||||
|
||||
iex> reduce_while_ok(1..5, 9, &(if &1 < 4, do: {:ok, &1 + &2}, else: {:error, &1}))
|
||||
{:error, 4}
|
||||
"""
|
||||
@spec reduce_while_ok(Enum.t(), any, (any, any -> Result.t())) :: Result.t()
|
||||
def reduce_while_ok(enumerable, initial_value, fun) when is_function(fun, 2) do
|
||||
Enum.reduce_while(enumerable, initial_value, fn elem, acc ->
|
||||
result =
|
||||
fun
|
||||
|> apply([elem, acc])
|
||||
|> assert_valid!()
|
||||
|
||||
case Proto.unwrap(result) do
|
||||
{:ok, acc} -> {:cont, acc}
|
||||
{:error, _} = error -> {:halt, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp assert_valid!(result) do
|
||||
if Proto.valid?(result),
|
||||
do: result,
|
||||
else: raise(ArgumentError, "Expected result, but got #{inspect(result)}")
|
||||
end
|
||||
end
|
7
test/upshot/result/enum_test.exs
Normal file
7
test/upshot/result/enum_test.exs
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule Upshot.Result.EnumTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Upshot.Result
|
||||
import Upshot.Result.Enum
|
||||
doctest Upshot.Result.Enum
|
||||
@moduledoc false
|
||||
end
|
Reference in a new issue