NextPipe (NextPipe v0.4.0)
Use import NextPipe
to make your pipelines a bit more flexible.
NextPipe
allows the chaining of functions with control through the idiomatic
{:ok, _}
and {:error, _}
tuples. In the case of a function returning a
value matching {:error, _}
, the pipeline is short-circuited.
value
|> next(fn ...)
|> next(fn ...)
|> try_next(fn ..., fn ...)
|> on_error(fn ...)
|> always(fn ...)
NextPipe
doesn't use macros or overridden operators. Like Kernel.then/2
,
the functions in NextPipe
work with function arguments and idiomatic tuples,
{:ok, _}
and {:error, _}
.
Use next/2
to conditionally execute its function argument based on the value
of the first argument.
If the first argument matches {:ok, _}
, the function passed as the second
argument will be called with the second element of the tuple passed in the
first argument. If the first argument matches {:error, _}
, the function will
not be called and the same tuple will be returned by next/2
.
Otherwise (like at the beginning of a pipeline), the function will be called
with value
.
try_next/3
works like next/2
but rescues exceptions. It accepts a third
optional argument, which is the function to be called in case an exception is
rescued.
Use always/2
to always call the function argument, but with the full
argument value, not just the second element of the tuple.
Use on_error/2
to respond to an error tuple.
as-an-alternative-to-with
As an alternative to with
The with
special form is often use to conditionally call functions if prior
functions are successful:
with {:ok, value} <- fn1(arg1),
{:ok, value} <- fn2(value, arg2) do
fn3(value)
else
{:error, error} -> error_fn(error)
end
With NextPipe
:
arg1
|> next(& fn1/1)
|> next(& fn2(&1, arg2))
|> next(& fn3/1)
|> on_error(& error_fn/1)
In the case of with
, if a function result does not match with the left side
of the <-
operator, the subsequent clauses are skipped. If there is an
else
in the with
it is used to handle the mismatch.
When using next/2
, if the argument matches {:error, _}
then the function
argument is not called and the first argument is returned unchanged. In this
way, all next/2
calls are effectively skipped in the pipeline.
If one of the functions may raise an exception, more boilerplate code is eliminated.
Compare using with
:
try do
with {:ok, value} <- fn1(arg1),
{:ok, value} <- fn2(value, arg2) do
fn3(value)
end
rescue
exception -> {:error, exception}
end
to the equivalent behavior using NextPipe
:
arg1
|> try_next(& fn1(&1))
|> try_next(& fn2(&1, arg2))
|> try_next(& fn3(&1))
as-an-alternative-to-ecto-multi
As an alternative to Ecto.Multi
Transaction control with Ecto.Multi
is quite powerful and flexible. It can,
however, be a bit cumbersome for simpler situations. And then
Repo.transaction/2
with a simple function requires some boilerplate code for
rescuing any exeptions if passing those up is undesirable. NextPipe
may
clean those cases up a bit.
Compare this use of Repo.transaction/2
:
def something(arg1, arg2) do
try do
Repo.transaction(fn repo ->
arg1
|> fn1(arg2)
|> fn2()
end)
rescue
exception -> {:error, exception}
end
end
And then using NextPipe
:
def something(arg1, arg2) do
Repo.transaction(fn repo ->
arg1
|> try_next(& fn1(&1, arg2))
|> try_next(& fn2(&1))
|> on_error(fn error -> repo.rollback(error))
end)
end
functions-with-multiple-arguments
Functions with multiple arguments
The function passed to next/2
et al accepts a single argument. If multiple
arguments are required, return a new function with those arguments bound.
As an example, consider the following traditional Elixir pipeline:
def something(arg1, arg2) do
arg1
|> fn1(arg2)
|> fn2()
end
The analogous pipeline using next/2
might be:
def something(arg1, arg2) do
arg1
|> next(& fn1(&1, arg2))
|> next(& fn2(&1))
end
Link to this section Summary
Functions
Always call always_fn
with the first argument. Unlike next
, the
always_fn
receives the full tuple passed as the first argument to
always/2
, i.e., {:ok, value}
or {:error, value}
.
Conditionally call the next function.
Process the enumerable against the function while the function returns {:ok, _}
tuples.
Wrap the argument in an {:ok, _}
tuple, if necessary. This is useful for
returning an {:ok, _}
tuple at the end of a pipeline instead of, for
example, |> then(&{:ok, &1})
An alias for next/2
.
The inverse of next/2
, if the first argument matches {:error, value}
, call
error_fn
with value
. Otherwise, return the first argument.
Conditionally call the next function and provide an outlet for handling
exceptions when next_fn
is called.
Link to this section Functions
always(value, always_fn)
@spec always( {:ok, any()} | {:error, any()}, always_fn :: (any() -> {:ok, any()} | {:error, any()}) ) :: {:ok, any()} | {:error, any()}
Always call always_fn
with the first argument. Unlike next
, the
always_fn
receives the full tuple passed as the first argument to
always/2
, i.e., {:ok, value}
or {:error, value}
.
always_fn
should return a value matching either {:ok, _}
or {:error, _}
.
This is basically the same as then/2
with a more strict type spec.
next(input, next_fn)
@spec next( {:ok, any()} | {:error, any()} | any(), next_fn :: (any() -> {:ok, any()} | {:error, any()}) ) :: {:ok, any()} | {:error, any()} | any()
Conditionally call the next function.
If the first argument matches {:ok, value}
, next_fn
is called with
value
.
If the first argument matches {:error, _}
, the call to next_fn
will be
skipped and the function returns the first argument unchanged.
Otherwise, the function is called with first argument unchanged. This supports
the use of next/2
at the beginning of a pipeline.
next/2
returns the value returned by next_fn
.
next_fn
typically returns {:ok, value}
if it was successful. Otherwise,
return {:error, value}
. It may also make sense for next_fn
to return a
value of a different shape, like in the last step of a pipeline.
next_while(enumerable, function)
@spec next_while(Enumerable.t(), (any() -> {:ok, any()} | {:error, any()})) :: {:ok, any()} | {:error, any()}
Process the enumerable against the function while the function returns {:ok, _}
tuples.
If all items are successful, the result is an {:ok, items}
tuple, where
items
is the list of values returned as the seccond item in the {:ok, item}
tuple from function
.
If any call to function is unsuccessful, the loop is halted and the return
value is {:error, {error, items}}
, where error
is the error returned from
the unsuccessful function call and items
is the list of items processed
successfully.
ok(ok)
Wrap the argument in an {:ok, _}
tuple, if necessary. This is useful for
returning an {:ok, _}
tuple at the end of a pipeline instead of, for
example, |> then(&{:ok, &1})
{:ok, value}
- return unchanged{:error, value}
- return unchangedvalue
- return{:ok, value}
ok(value, function)
@spec ok( {:ok, any()} | {:error, any()} | any(), ok_fn :: (any() -> {:ok, any()} | {:error, any()}) ) :: {:ok, any()} | {:error, any()} | any()
An alias for next/2
.
on_error(input, error_fn)
@spec on_error( {:ok, any()} | {:error, any()}, error_fn :: (any() -> {:ok, any()} | {:error, any()}) ) :: {:ok, any()} | {:error, any()} | any()
The inverse of next/2
, if the first argument matches {:error, value}
, call
error_fn
with value
. Otherwise, return the first argument.
try_next(value, next_fn, rescue_fn \\ fn _value, exception -> {:error, exception} end)
@spec try_next( {:ok, any()} | {:error, any()} | any(), next_fn :: (any() -> {:ok, any()} | {:error, any()}), rescue_fn :: (any(), any() -> {:ok, any()} | {:error, any()}) ) :: {:ok, any()} | {:error, any()}
Conditionally call the next function and provide an outlet for handling
exceptions when next_fn
is called.
This function generally behaves the same as next/2
unless an exception is
raised in next_fn
.
In that case, the exception is rescued and rescue_fn
is called with two
arguments: the second element of the {:ok, value}
tuple and the raised
exception. The value returned from try_next/3
is then the value returned
from rescue_fn
. rescue_fn
should return a value matching {:ok, _}
or
{:error, _}
.
The default implementation of rescue_fn
simply returns an {:error, exception}
tuple, where exception
is the exception raised by next_fn
.