DEV Community

loading...

Elixir’s with statement is fantastic

aloukissas profile image Alex Loukissas ・2 min read

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.

Discussion (1)

pic
Editor guide
Collapse
martinthenth profile image
Martin • Edited

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