I gotta be honest. It was not exactly love at first sight when I first encountered Elixir’s with
statement. But just like I learned to appreciate (and love) the whole non-defensive/let-it-crash approach, the same happened here as well.
The result: I was able to rewrite a bunch of complicated functions with very hard-to-read nested case
and/or cond
statements in a very clean, and, more importantly, maintainable and extensible way using with
.
Basic usage
The basic premise of with
is that it lets you chain together pattern matches and arriving at the positive result, in a very concise statement. Error handling (if any — remember, this is still let-it-crash land) is done in an optional else
clause, after we’re done with the positive codepath.
Let’s look at a basic example. Suppose you’re working with the Elixir library for Stripe and you want to implement a function that creates a new subscription for a customer. Here’s what this may have looked if we hadn’t used the with
statement:
What’s wrong with the code above? Here’s a hint:
Let’s try to write the same function using the with
statement:
That’s a lot cleaner and easier to read, no?
Alas, it’s not all rosy
But there’s a problem. In the second implementation, it’s really hard to infer which of the steps of the function calls failed the pattern matching, so that we can return a more useful error to the caller. It also makes it harder to debug.
So, did we trade readability for usability?
Not quite.
We can do better!
Fear not, reader! A really simple solution that we’ve been leveraging heavily in our codebase at AgentRisk is to leverage pattern matching. Here’s how:
Each chained function call returns a tuple with an atom that helps us know exactly where things went wrong in the chain.
If we rewrite the previous code example like this, this is what we get:
In the example above, we are swallowing the error return values, but this was in favor of simplicity. The core idea here is that we tag each step with a unique tuple pattern, so that we can know exactly where the error occurred.
A final note
Hopefully, this post clarified a few of the nuances of Elixir’s with statement. We have not found a better way to implement complex functions in our codebase, for readability, maintainability, and ease of debugging.
I want to thank Carlos Brito Lage for introducing me to this pattern as well as editing an early version of this post.
This post was originally posted on the AgentRisk blog.
Top comments (2)
What do you think about section
Avoid else in with blocks
in keathley.io/blog/good-and-bad-elix... ?This is a golden nugget. It makes "with" code blocks way more readable, and it makes Dialyzer's false-negatives "The pattern can never match the type" errors go away haha