on

# Elixir's Secret Weapon

I recently began using a new(ish) feature of Elixir that completely transformed
the way I build programs. I’m talking about the special form `with`

. It can feel
unfamiliar at first, but it is extremely powerful and flexible. This article
will explain how `with`

works and how it can be used to make your code more
robust to errors. First, though, let’s look at the problem `with`

is trying to
solve.

# Tagged Tuples

The Elixir community has borrowed an idiom from the Erlang community called
tagged tuples. Using this approach, our functions will return a tuple where the
first value is an atom describing the “type” of the result. A common pattern is
returning `{:ok, value}`

for a successful response and `{:error, some_error}`

for an error response.

```
defmodule Math do
def divide(_, b) when b == 0, do: {:error, "divide by zero"}
def divide(a, b), do: {:ok, a / b}
end
case Math.divide(1, 2) do
{:ok, result} -> "success: #{result}"
{:error, error} -> "error: #{error}"
end
# => "success: 0.5"
```

This is nice for a few reasons. First, it allows us to treat errors as values. We know that our function will always return even when provided semantically invalid data. Second, we can use pattern matching to act explicitly on both the success and failure case.

This patterns starts to break down when we want to perform multiple, dependent actions that can all fail. Suppose, for example, that we’d like to divide two numbers and, if successful, divide the result by a third number.

```
case Math.divide(1, 2) do
{:ok, result} ->
case Math.divide(result, 4) do
{:ok, result} -> "success: #{result}"
{:error, error} -> "error: #{error}"
end
{:error, error} -> "error: #{error}"
end
# => "success: 0.125"
```

This works, sure, but it’s becoming challenging to read. You can easily imagine the mess this becomes with an arbitrarily long list of dependent operations.

What should we do? We want to continue using this pattern because it is safe and
explicit but it feels ugly and unreadable with real-world examples. The answer,
of course, is to use `with`

.

# Using `with`

The `with`

special form allows you to define a set of operations to perform and
associated patterns to match their results against. Each operation can use
bindings from the pattern match of the previous operations. If any of the
matches fail, the entire `with`

form stops and that non-matching result is
returned. Let’s explore by rewriting the above example using `with`

.

```
with {:ok, a} <- Math.divide(1, 2),
{:ok, b} <- Math.divide(a, 4) do
"success: #{b}"
end
# => "success: 0.125"
```

Let’s walk through the execution. First, we call `Math.divide(1, 2)`

. The result
is `{:ok, 0.5}`

. The `with`

form checks to see if this matches the pattern on
the lefthand side of `<-`

. It does, so the variable `a`

is bound to `0.5`

and
execution continues. On the second line, we run `Math.divide(0.5, 4)`

(because
`a`

is now bound to `0.5`

). This returns `{:ok, 0.125}`

. We check if it matches
the pattern on the lefthand side of its `<-`

. It does, so `b`

is bound to
`0.125`

. There are no more operations to perform, so the body of the `do`

block
is executed. This `do`

block can use any of the bindings from the `with`

operations above. It uses `b`

to return the string `"success: 0.125"`

.

Now, let’s try walking through an error case.

```
with {:ok, a} <- Math.divide(1, 0),
{:ok, b} <- Math.divide(a, 4) do
"success: #{b}"
end
# => {:error, "divide by zero"}
```

First, we call `Math.divide(1, 0)`

. The result is `{:error, "divide by zero"}`

.
We check to see if this matches the patter on the left of `<-`

. It doesn’t! As
soon as this mismatch occurs, the `with`

form immediately stops executing
further operations and returns the result that did not match. Therefore, the
return value of the form is `{:error, "divide by zero"}`

.

# Better Error Handling

We’re already in a better spot by using `with`

but we can go even further. The
`with`

form allows us to describe an `else`

clause that handles any non-matching
values rather than simply returning them. Let’s add one.

```
with {:ok, a} <- Math.divide(1, 0),
{:ok, b} <- Math.divide(a, 4) do
"success: #{b}"
else
{:error, error} -> "error: #{error}"
end
# => "error: divide by zero"
```

Now, when the first operation returns `{:error, "divide by zero"}`

and it
doesn’t match the pattern, the value is passed to the `else`

block. Next, we
check each clause of the `else`

block in order (in this example there is only
one clause). The first clause matches `{:error, "divide by zero"}`

, so the
string `"divide by zero"`

is bound to `error`

. Finally, the body of that clause
is executed with the bindings from the match. It returns the string ```
"error:
divide by zero"
```

.

# More

The `with`

form allows even more flexibility, including guard clauses in each of
the operations. For more examples see the
docs.