Kernel.SpecialForms.with
with
, go back to Kernel.SpecialForms module for more information.
Used to combine matching clauses.
Let's start with an example:
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
{:ok, 150}
If all clauses match, the do
block is executed, returning its result.
Otherwise the chain is aborted and the non-matched value is returned:
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> end
:error
Guards can be used in patterns as well:
iex> users = %{"melany" => "guest", "bob" => :admin}
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob") do
...> {:ok, to_string(role)}
...> end
{:ok, "admin"}
As in for/1
, variables bound inside with/1
won't leak.
Expressions without <-
may also be used in clauses. For instance,
you can perform regular matches with the =
operator:
iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, double_width * height}
...> end
{:ok, 300}
iex> width
nil
The behaviour of any expression in a clause is the same as outside.
For example, =
will raise a MatchError
instead of returning the
non-matched value:
with :foo = :bar, do: :ok
** (MatchError) no match of right hand side value: :bar
As with any other function or macro call in Elixir, explicit parens can
also be used around the arguments before the do
/end
block:
iex> opts = %{width: 10, height: 15}
iex> with(
...> {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height)
...> ) do
...> {:ok, width * height}
...> end
{:ok, 150}
The choice between parens and no parens is a matter of preference.
An else
option can be given to modify what is being returned from
with
in the case of a failed match:
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> else
...> :error ->
...> {:error, :wrong_data}
...>
...> _other_error ->
...> :unexpected_error
...> end
{:error, :wrong_data}
The else
block works like a case
clause: it can have multiple clauses,
and the first match will be used. Variables bound inside with
(such as
width
in this example) are not available in the else
block.
If an else
block is used and there are no matching clauses, a WithClauseError
exception is raised.