View Source Bunch (Bunch v1.3.1)

A bunch of general-purpose helper and convenience functions.

Link to this section Summary

Functions

Imports a bunch of Bunch macros: withl/1, withl/2, ~>/2, ~>>/2, quote_expr/1, quote_expr/2

Returns an :error tuple if given value is nil and :ok tuple otherwise.

Extracts the key from a key-value tuple.

Embeds the argument in a one-element list if it is not a list itself. Otherwise works as identity.

Creates a short reference.

Works like quote/2, but doesn't require a do/end block and options are passed as the last argument.

Returns given stateful try value along with its status.

Extracts the value from a key-value tuple.

Works like withl/2, but allows shorter syntax.

A labeled version of the with/1 macro.

Helper for writing pipeline-like syntax. Maps given value using match clauses or lambda-like syntax.

Works similar to ~>/2, but accepts only -> clauses and appends default identity clause at the end.

Link to this section Functions

Link to this macro

__using__(args)

View Source (macro)

Imports a bunch of Bunch macros: withl/1, withl/2, ~>/2, ~>>/2, quote_expr/1, quote_expr/2

@spec error_if_nil(value, reason) :: Bunch.Type.try_t(value)
when value: any(), reason: any()

Returns an :error tuple if given value is nil and :ok tuple otherwise.

examples

Examples

iex> map = %{:answer => 42}
iex> Bunch.error_if_nil(map[:answer], :reason)
{:ok, 42}
iex> Bunch.error_if_nil(map[:invalid], :reason)
{:error, :reason}
@spec key({key, value}) :: key when key: any(), value: any()

Extracts the key from a key-value tuple.

@spec listify(l) :: l when l: list()
@spec listify(a) :: [a] when a: any()

Embeds the argument in a one-element list if it is not a list itself. Otherwise works as identity.

Works similarly to List.wrap/1, but treats nil as any non-list value, instead of returning empty list in this case.

examples

Examples

iex> Bunch.listify(:a)
[:a]
iex> Bunch.listify([:a, :b, :c])
[:a, :b, :c]
iex> Bunch.listify(nil)
[nil]
@spec make_short_ref() :: Bunch.ShortRef.t()

Creates a short reference.

Link to this macro

quote_expr(code, opts \\ [])

View Source (macro)

Works like quote/2, but doesn't require a do/end block and options are passed as the last argument.

Useful when quoting a single expression.

examples

Examples

iex> use Bunch
iex> quote_expr(String.t())
quote do String.t() end
iex> quote_expr(unquote(x) + 2, unquote: false)
quote unquote: false do unquote(x) + 2 end

nesting

Nesting

Nesting calls to quote disables unquoting in the inner call, while placing quote_expr in quote or another quote_expr does not:

iex> use Bunch
iex> quote do quote do unquote(:code) end end == quote do quote do :code end end
false
iex> quote do quote_expr(unquote(:code)) end == quote do quote_expr(:code) end
true
Link to this function

stateful_try_with_status(res)

View Source
@spec stateful_try_with_status(result) :: {status, result}
when status: Bunch.Type.try_t(),
     result:
       Bunch.Type.stateful_try_t(state :: any())
       | Bunch.Type.stateful_try_t(value :: any(), state :: any())

Returns given stateful try value along with its status.

@spec value({key, value}) :: value when key: any(), value: any()

Extracts the value from a key-value tuple.

Link to this macro

withl(keyword)

View Source (macro)
@spec withl(
  keyword :: [
    {key :: atom(), with_clause :: term()}
    | {:do, code_block :: term()}
    | {:else, match_clauses :: term()}
  ]
) :: term()

Works like withl/2, but allows shorter syntax.

examples

Examples

iex> use Bunch
iex> x = 1
iex> y = 2
iex> withl a: true <- x > 0,
...>       b: false <- y |> rem(2) == 0,
...>       do: {x, y},
...>       else: (a: false -> {:error, :x}; b: true -> {:error, :y})
{:error, :y}

For more details and more verbose and readable syntax, check docs for withl/2.

Link to this macro

withl(with_clauses, list)

View Source (macro)
@spec withl(keyword(with_clause :: term())) do
  code_block :: term()
else
  match_clauses :: term()
end :: term()

A labeled version of the with/1 macro.

This macro works like with/1, but enforces user to mark corresponding withl and else clauses with the same label (atom). If a withl clause does not match, only the else clauses marked with the same label are matched against the result.

iex> use Bunch
iex> names = %{1 => "Harold", 2 => "Małgorzata"}
iex> test = fn id ->
...>   withl id: {int_id, _} <- Integer.parse(id),
...>         name: {:ok, name} <- Map.fetch(names, int_id) do
...>     {:ok, "The name is #{name}"}
...>   else
...>     id: :error -> {:error, :invalid_id}
...>     name: :error -> {:error, :name_not_found}
...>   end
...> end
iex> test.("1")
{:ok, "The name is Harold"}
iex> test.("5")
{:error, :name_not_found}
iex> test.("something")
{:error, :invalid_id}

withl clauses using no <- operator are supported, but they also have to be labeled due to Elixir syntax restrictions.

iex> use Bunch
iex> names = %{1 => "Harold", 2 => "Małgorzata"}
iex> test = fn id ->
...>   withl id: {int_id, _} <- Integer.parse(id),
...>         do: int_id = int_id + 1,
...>         name: {:ok, name} <- Map.fetch(names, int_id) do
...>     {:ok, "The name is #{name}"}
...>   else
...>     id: :error -> {:error, :invalid_id}
...>     name: :error -> {:error, :name_not_found}
...>   end
...> end
iex> test.("0")
{:ok, "The name is Harold"}

All the withl clauses that use <- operator must have at least one corresponding else clause.

iex> use Bunch
iex> try do
...>   Code.compile_quoted(quote do
...>     withl a: a when a > 0 <- 1,
...>           b: b when b > 0 <- 2 do
...>       {:ok, a + b}
...>     else
...>       a: _ -> :error
...>     end
...>   end)
...> rescue
...>   e -> e.description
...> end
"Label `:b` not present in withl else clauses"

variable-scoping

Variable scoping

Because the labels are resolved in the compile time, they make it possible to access results of already succeeded matches from else clauses. This may help handling errors, like below:

iex> use Bunch
iex> names = %{1 => "Harold", 2 => "Małgorzata"}
iex> test = fn id ->
...>   withl id: {int_id, _} <- Integer.parse(id),
...>         do: int_id = int_id + 1,
...>         name: {:ok, name} <- Map.fetch(names, int_id) do
...>     {:ok, "The name is #{name}"}
...>   else
...>     id: :error -> {:error, :invalid_id}
...>     name: :error -> {:ok, "The name is Defaultius the #{int_id}th"}
...>   end
...> end
iex> test.("0")
{:ok, "The name is Harold"}
iex> test.("5")
{:ok, "The name is Defaultius the 6th"}

duplicate-labels

Duplicate labels

withl supports marking multiple withl clauses with the same label, however in that case all the else clauses marked with such label are simply put multiple times into the generated code. Note that this may lead to confusion, in particular when variables are rebound in withl clauses:

iex> use Bunch
iex> test = fn x ->
...>   withl a: x when x > 1 <- x,
...>         do: x = x + 1,
...>         a: x when x < 4 <- x do
...>     :ok
...>   else
...>     a: x -> {:error, x}
...>   end
...> end
iex> test.(2)
:ok
iex> test.(1)
{:error, 1}
iex> test.(3)
{:error, 4}
Link to this macro

expr ~> mapper

View Source (macro)

Helper for writing pipeline-like syntax. Maps given value using match clauses or lambda-like syntax.

examples

Examples

iex> use Bunch
iex> {:ok, 10} ~> ({:ok, x} -> x)
10
iex> 5 ~> &1 + 2
7

Lambda-like expressions are not converted to lambdas under the hood, but result of expr is injected to &1 at the compile time.

Useful especially when dealing with a pipeline of operations (made up e.g. with pipe (|>) operator) some of which are hard to express in such form:

iex> use Bunch
iex> ["Joe", "truck", "jacket"]
...> |> Enum.map(&String.downcase/1)
...> |> Enum.filter(& &1 |> String.starts_with?("j"))
...> ~> ["Words:" | &1]
...> |> Enum.join("\n")
"Words:
joe
jacket"
Link to this macro

expr ~>> mapper_clauses

View Source (macro)

Works similar to ~>/2, but accepts only -> clauses and appends default identity clause at the end.

examples

Examples

iex> use Bunch
iex> {:ok, 10} ~>> ({:ok, x} -> {:ok, x+1})
{:ok, 11}
iex> :error ~>> ({:ok, x} -> {:ok, x+1})
:error