Packages
reactor
0.4.1
1.0.2
1.0.1
1.0.0
0.17.0
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
An asynchronous, graph-based execution engine
Current section
Files
Jump to
Current section
Files
lib/reactor/utils.ex
defmodule Reactor.Utils do
@moduledoc false
@doc """
Recursively merge maps.
"""
@spec deep_merge(map, map) :: map
def deep_merge(lhs, rhs) when is_map(lhs) and is_map(rhs) do
Map.merge(lhs, rhs, fn
_key, lvalue, rvalue when is_map(lvalue) and is_map(rvalue) ->
deep_merge(lvalue, rvalue)
_key, _lvalue, rvalue ->
rvalue
end)
end
@doc """
Append a non-nil value to the end of the enumerable.
"""
@spec maybe_append(Enumerable.t(), any) :: Enumerable.t()
def maybe_append(collection, nil), do: collection
def maybe_append(collection, value), do: Enum.concat(collection, [value])
@doc """
Append a non-nil result of the callback function to the enumerable.
"""
@spec maybe_append_result(Enumerable.t(), (-> any)) :: Enumerable.t()
def maybe_append_result(collection, callback) do
case callback.() do
nil -> collection
value -> Enum.concat(collection, [value])
end
end
@doc """
A joiner that replaces the last join value with a different one.
"""
@spec sentence(Enumerable.t(), (any -> binary), binary, binary) :: binary
def sentence(enumerable, mapper \\ &"`#{&1}`", joiner \\ ", ", last_joiner \\ " or ")
def sentence(enumerable, mapper, joiner, last_joiner),
do:
enumerable
|> Enum.to_list()
|> do_sentence([], mapper, joiner, last_joiner)
defp do_sentence([], [], _, _, _), do: []
defp do_sentence([value], [], mapper, _, _), do: [mapper.(value)]
defp do_sentence([value], zipped, mapper, _joiner, last_joiner) do
[mapper.(value), last_joiner | zipped]
|> Enum.reverse()
|> Enum.join()
end
defp do_sentence([head | tail], [], mapper, joiner, last_joiner),
do: do_sentence(tail, [mapper.(head)], mapper, joiner, last_joiner)
defp do_sentence([head | tail], zipped, mapper, joiner, last_joiner),
do: do_sentence(tail, [mapper.(head), joiner | zipped], mapper, joiner, last_joiner)
@doc """
Perform a map over an enumerable provided that the mapper function continues
to return ok tuples.
"""
@spec map_while_ok(Enumerable.t(input), (input -> {:ok, output} | {:error, any}), boolean) ::
{:ok, Enumerable.t(output)} | {:error, any}
when input: any, output: any
def map_while_ok(inputs, mapper, preserve_order? \\ false)
def map_while_ok(inputs, mapper, false) when is_function(mapper, 1) do
reduce_while_ok(inputs, [], fn input, acc ->
case mapper.(input) do
{:ok, value} -> {:ok, [value | acc]}
{:error, reason} -> {:error, reason}
end
end)
end
def map_while_ok(inputs, mapper, true) do
case map_while_ok(inputs, mapper, false) do
{:ok, outputs} -> {:ok, Enum.reverse(outputs)}
{:error, reason} -> {:error, reason}
end
end
@doc """
A raising version of `map_while_ok/2..3`.
"""
@spec map_while_ok!(Enumerable.t(input), (input -> {:ok, output} | {:error, any}), boolean) ::
Enumerable.t(output) | no_return
when input: any, output: any
def map_while_ok!(inputs, mapper, preserve_order? \\ false) do
case map_while_ok(inputs, mapper, preserve_order?) do
{:ok, outputs} -> outputs
{:error, reason} -> raise(reason)
end
end
@doc """
Perform a reduction over an enumerable provided that the reduction function
returns an ok tuple.
"""
@spec reduce_while_ok(Enumerable.t(input), acc, (input, acc -> {:ok, acc} | {:error, any})) ::
{:ok, acc} | {:error, any}
when input: any, acc: any
def reduce_while_ok(inputs, default \\ [], reducer) when is_function(reducer, 2) do
Enum.reduce_while(inputs, {:ok, default}, fn input, {:ok, acc} ->
case reducer.(input, acc) do
{:ok, acc} -> {:cont, {:ok, acc}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
@doc """
A raising version of `reduce_while_ok/3`.
"""
@spec reduce_while_ok!(Enumerable.t(input), acc, (input, acc -> {:ok, acc} | {:error, any})) ::
acc | no_return
when input: any, acc: any
def reduce_while_ok!(inputs, default \\ [], reducer) do
case reduce_while_ok(inputs, default, reducer) do
{:ok, acc} -> acc
{:error, reason} -> raise(reason)
end
end
@doc """
Apply the input function to an `ok` tuple, passing an error tuple through
unchanged.
"""
@spec and_then({:ok, input} | {:error, reason}, (input -> {:ok, output} | {:error, reason})) ::
{:ok, output} | {:error, reason}
when input: any, output: any, reason: any
def and_then({:ok, input}, transform) when is_function(transform, 1) do
case transform.(input) do
{:ok, output} -> {:ok, output}
{:error, reason} -> {:error, reason}
end
end
def and_then({:error, reason}, _transform), do: {:error, reason}
@type argument_error :: %ArgumentError{
__exception__: true,
message: binary
}
@doc """
A wrapper for defining an ArgumentError with a consistent error message format.
"""
@spec argument_error(String.Chars.t(), String.Chars.t()) :: argument_error
def argument_error(argument_name, reason),
do: ArgumentError.exception(message: "`#{argument_name}` #{reason}")
@doc """
A wrapper for defining an ArgumentError with a consistent error message format.
"""
@spec argument_error(String.Chars.t(), String.Chars.t(), any) :: argument_error
def argument_error(argument_name, reason, value) do
ArgumentError.exception(
message: """
`#{argument_name}` #{reason}
## Value of `#{argument_name}`
```
#{inspect(value)}
```
"""
)
end
end