# 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.