Using tuple-wrapping to improve `with` expressions in Elixir

Wrap your return values in tuples so you can clearly match them in the else block

The with expression allows us to declare clauses and their expected results. The clauses are executed in order until the end of the list, or until one clause doesn’t match with its expected result. Here is the example from the Elixir documentation:

A simple `with` expression

In this example, we are calculating the area of a rectangle. If everything works out we will return {:ok, area}, if not, we will fall through to the else block and return the error tuple {:error, :wrong_data}.

But what happens if we want to send back a better error message? Right now we fall through to the else block with an :error atom that is returned from Map.fetch/2, but we don’t now which clause it came from (was the :width missing? or was the :height missing?).

To know which clause is failing, we can wrap each side in a tuple that describes the clause, and then match in the else block:

A `with` expression with improved error responses via tuple-wrapping

This “tuple-wrapping” technique borders on the functional programming concept of “Monads”, but is not something I am going to be discussing here.


Lets look at a slightly more complicated example. Here we have a function that is called when a user wants to join some sort of online game. There are few checks we need to do:

A `with` expression with more clauses

Like the previous example, we have ambiguity in the else block because the Game.is_full?/1 clause and the Game.is_started?/1 clause both return the same value. In addition, we have another boolean value coming from the User.has_permission?/2 clause, which makes the entire else block hard to understand.

Lets clean this up using the tuple-wrapping. Observe how it makes the else block easier to understand, and how it gives the ability to return fine-grain error tuples:

A `with` expression with fine-grain error tuples

In the past I thought this tuple-wrapping was ugly. So I tried to tuck it away inside the functions that I used in the clauses. This ended up being bad. The problems were:

  1. If the function I was using was from an Ecto repo, I couldn’t change the function’s return value, so I had to add another layer of function wrappers around the Ecto ones. This led to way too much extra code.
  2. If a function was used inside a with expression and now returns a tuple with its “name”, all of the other places in the code that call that function now have to deal with a tuple that they don’t care about. For example:
A function that has been modified to return a tuple for a `with` expression now becomes annoying to use elsewhere in the code

For posterity, here is an example of problem 1. Some people may prefer doing it this way for all of their clauses, but I found this led to too much extra code (every with expression now means defining new private functions). Additionally, having the tuple wrapping in the with expression makes it easier to tell where the error cases in the else block originate from.

Here is an example anyways:

An example of using private functions to do the tuple wrapping, which may be preferable for some people

There may be cases where you don’t care about sending back specific error tuples, and simply want to know if a whole set of clauses succeeded or not. In this case, using the tuple-wrapping would unnecessary:

An example of when not to use tuple-wrapping: when you don’t care about specific errors

Wrapping up, the takeaway is that if you want fine-grained error values from with expressions with ambiguous clauses, then you should apply tuple-wrapping inside of the with expression.