Railway development in Elixir using `with`

A year ago, I came across the great post by Zohaib Rauf on Railway programming in Elixir. Here was the code Zohaib used as an example on the post:

request  
 |> validate_request
 |> get_user
 |> update_db
 |> send_email
 |> return_http_message

See the problem? What if get_user, update_db, or send_email fails? How do we handle the error?

Here's one of the great illustration by Zohaib to show the problem above:

Request exit early Picture from Zohaib Rauf from this blog post

Handle success and errors with with

Fortunately, in Elixir 1.2, we get the with keyword. The with keyword lets us handle these scenarios with ease. Hat tip to trenpixster for the great blog post which got me thinking about this.

Let's return to the example above and use the with keyword

def handle_request(request) do  
  with {:ok} <- validate_request(request),
       {:ok, user} <- get_user(request),
       {:ok} <- update_db(user),
       {:ok} <- send_email(user) do
       return_http_message
  else
    {:error, reason} -> handle_error(reason)
    _ -> handle_ambigous_error
    # alternately, you could handle the errors
    # {:error, :update_db, details} -> handle_update_db_error(details)
    # {:error, :send_email, details} -> handle_send_email_error(details)
  end
end  

Let's explore what's going on: the with block has 4 expressions. Each one will execute and the return will be matched. So, the output of validate_request will be matched against the 1-tuple {:ok}. If the value is {:ok}, get_user(request) will be executed. If not, we'll go to our else block.

[EDIT] It's not until Elixir 1.3 that we get the else block. Everything except the else block works in Elixir 1.2. Thanks @fish0398 for the catch.

Let's look at what we get by using the with:

  • Obvious business logic It's really easy to see the happy path and what the method is trying to do.
  • Obvious error handling All the possible error states are specified and easy to understand.

If you find yourself with some code that needs to have multiple outputs, try using the with keyword to clean it up.

Scott Messinger

Read more posts by this author.