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
cond statements in a very clean, and, more importantly, maintainable and extensible way using
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
What’s wrong with the code above? Here’s a hint:
Let’s try to write the same function using the
That’s a lot cleaner and easier to read, no?
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?
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.
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.