View Source Bunch (Bunch v1.6.1)
A bunch of general-purpose helper and convenience functions.
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.
Maps a value x
with a function
if the condition is true, acts as
an identity function otherwise.
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.
Functions
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
iex> map = %{:answer => 42}
iex> Bunch.error_if_nil(map[:answer], :reason)
{:ok, 42}
iex> Bunch.error_if_nil(map[:invalid], :reason)
{:error, :reason}
Extracts the key from a key-value tuple.
@spec listify(a | [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
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.
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
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 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
@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.
Maps a value x
with a function
if the condition is true, acts as
an identity function otherwise.
Examples
iex> use Bunch
iex> then_if(1, false, & &1 + 1)
1
iex> then_if(1, true, & &1 + 1)
2
iex> arg = 1
iex> arg |> then_if(not is_list(arg), fn arg -> [arg] end) |> Enum.map(&(&1*2))
[2]
Extracts the value from a key-value tuple.
@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
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
.
@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
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
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}
Helper for writing pipeline-like syntax. Maps given value using match clauses or lambda-like syntax.
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"
Works similar to ~>/2
, but accepts only ->
clauses and appends default
identity clause at the end.
Examples
iex> use Bunch
iex> {:ok, 10} ~>> ({:ok, x} -> {:ok, x+1})
{:ok, 11}
iex> :error ~>> ({:ok, x} -> {:ok, x+1})
:error