Recently I wrote an article on using Elixir’s with syntax:

I ended that article saying that I was sure there was uses for with , but I was struggling to find them. As an update to that article I have found an excellent use for the with statement, model sanitization!

Here is a flow diagram displaying the logic that will be written in multiple ways for this post:

As you can see the logic branches off to two different final conclusions, either the parameters are valid and a user can be created, or there was an error which should be communicated to the user. What I’m looking for in the code is for this idea to be expressed as clearly and concisely as possible.

Below is an example of using with , it creates a user through the use of a struct, whilst sanitizing each parameter in turn that comes in from the request:

Above I have defined the function create/1 which takes a map of user properties including "Name" and "Email" and returns a created User struct. With each parameter that is added, it is put through a validation function that will either return {:ok, property} or {:error, message} .

Using with these functions can be chained together, sharing a single else clause if something goes wrong. Also, the values produced from each function can be captured and all used in the do statement, as shown above.

Also note, the else clause is actually optional in the code above. Because the clause returns what it captures, that behaviour is maintained when the else clause is omitted.

Comparing the create/1 function to the flow diagram, it is clear to see that using with allowed that logic to be succinctly expressed. It clearly shows the happy path the logic wants to go down, with the resulting actions of whether it reaches the end of that path or not.

Let’s look at alternatives to with , as shown in the previous article they are to use functions or case . Having looked over both alternatives again, I cannot see that using functions alone is a legitimate alternative, as it cannot properly express the branching logic of what is going on.

Below is an example of the logic expressed using case :

There are two crucial problems with the approach above. Firstly and most importantly, the most important line of code in create/1 function, the line that creates a User struct is hidden deep in nesting in the middle of the function. At the moment this is not very readable with 2 parameters, imagine if it had a more common amount of parameters, say 10? That would look horrific. The other problem is I’m having to redefine what to do with an error over and over again, rather than just having to specify it once for everything.