Pipehammer v0.1.1 Pipehammer View Source

Pipehammer, the simplest pipeline management.

You may now declare regular functions, and Pipehammer will write boilerplate for you.

Example:

defpipe safe_div(x, 0) do
  {:error, :division_by_zero}
end

defpipe safe_div(x, y) do
  {:ok, x / y}
end

This will define a function having this type signature:

@spec safe_div(any | {:ok, any} | {:error, any} ::
  {:ok, any} |
  {:error, :division_by_zero} |
  {:error, any }
12 |> safe_div(6) # => {:ok, 2.0}
12 |> safe_div(0) # => {:error, :division_by_zero}
{:ok, 12} |> safe_div(6) # => {:ok, 2.0}
{:error, :not_a_number} |> safe_div(6) # => {:error, :not_a_number}

This has immediate application in building pipelines:

defp issue_referral_voucher(account_id, referrer_reference) do
  find_account(account_id)
  |> set_account_referrer(referrer_reference)
  |> issue_referral_voucher()
  |> create_activity()
  |> publish_event()
  |> handle_error(fn e ->
    {:error, "bad error"}
  end)
end

You can still use with macro if you wish:


defp issue_referral_voucher(account_id, referrer_reference) do
  with {:ok, acc} <- find_account(account_id),
       {:ok, acc} <- set_account_referrer(acc, referrer_reference),
       {:ok, vou} <- issue_referral_voucher(acc),
       {:ok, act} <- create_activity(vou) do

    publish_event(act)
  else
    {:error, e} -> {:error, "bad error"}
  end
end

You can still compose the functions as usual:

set_account_referrer(find_account(account_id), referrer_reference)

Error handling

Pipe functions are expected to return {:error, e} tuples in error cases.

If additional information is required, consider wrapping it into a tuple: {:error, {:foo, :bar}}

If pipe function returns anything other than {:ok, any} | {:error, any} the behaviour of Pipehammer is undefined (means, the pipeline will crash in production))

What boilerplate to expect:

defpipe partial(x, y) when is_integer(x) do
  {:ok, x + y}
end

Will create these functions, in order:

def partial({:error, e}, _) do
  {:error, e}
end

def partial({:ok, x}, y) when is_integer(x) do
  partial(x, y)
end

def partial(x, y) when is_integer(x) do
  {:ok, x + y}
end

Note that error case intentionally does not have guards, as it does not care what you pass into the function, as long as 1st argument was an error. This logic is essential to chaining the steps.

Pipehammer is smart to eliminate duplicate error declarations:

defpipe foo(x, 0) do
  {:ok, x}
end

defpipe foo(x, 1) do
  {:ok, x}
end

This will still write one error clause:

def foo({:error, e}, _) do
  {:error, e}
end

Link to this section Summary

Functions

Alternative piping: it first step fails, try another one.

Declares function clauses that will handle nil, {:ok, _}, {:error, _} in addition to its main clause.

Handles errors, but leaves ok tuples intact

Link to this section Functions

Alternative piping: it first step fails, try another one.

Link to this macro

defpipe(decl, list)

View Source (macro)

Declares function clauses that will handle nil, {:ok, _}, {:error, _} in addition to its main clause.

Link to this function

handle_error(exp, handler)

View Source

Handles errors, but leaves ok tuples intact