with
clause is used to combine matching clauses.
It looks like we combine anonymous functions or handle function with multiple bodies (matching clauses).
Consider the case: we create a user, insert it into DB, then create greet email and then send it to the user.
Without the with
clause we might write something like this (I omitted functions implementations):
case create_user(user_params) do
{:ok, user} ->
case Mailer.compose_email(user) do
{:ok, email} ->
Mailer.send_email(email)
{:error, reason} ->
handle_error
end
{:error, changeset} ->
handle_error
end
Here we handle our business process's flow with case
(it could be cond
or if
). That leads us to so-called 'pyramid of doom', because we have to deal with possible conditions and decide: whether move further or not. It would be much nicer to rewrite this code with with
statement:
with {:ok, user} <- create_user(user_params),
{:ok, email} <- Mailer.compose_email(user) do
{:ok, Mailer.send_email}
else
{:error, _reason} ->
handle_error
end
In the code snippet above we've rewrite nested case
clauses with with
. Within with
we invoke some functions (either anonymous or named) and pattern match on their outputs. If all matched, with
return do
block result, or else
block result otherwise.
We can omit else
so with
will return either do
block result or the first fail result.
So, the value of with
statement is its do
block result.