Skip to content

Instantly share code, notes, and snippets.

@jmilamwalters
Last active December 28, 2018 21:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmilamwalters/8c3f39b748ee898a6c2f5bac7ffbf062 to your computer and use it in GitHub Desktop.
Save jmilamwalters/8c3f39b748ee898a6c2f5bac7ffbf062 to your computer and use it in GitHub Desktop.
Functions: `with` statement

Functions: with statement

A compilation of my favorite functions and patterns in functional programming

The with statement may be my favorite feature of Elixir since the pipe operator. It enables the chaining of uncertainty. It affords elegance to the otherwise unsavory business of nested control structures.

There are few things in code more corrupting of beauty than:

case fetch_token() do
  {:ok, token} ->
    case call_outside_server(token) do
      {:ok, resp} ->
        case write_to_db(resp) do
          {:ok, db_entry} -> db_entry
          error -> error
        end

      error ->
        error
    end

  error ->
    error
end

I encounter this pattern all the time, and I never fail to shudder. Let's try again:

with {:ok, token} <- fetch_token(),
     {:ok, resp} <- call_outside_server(token),
     {:ok, db_entry} <- write_to_db(resp) do
  db_entry
else
  error -> error
end

What's happening? with evaluates each expression to the right of <-. Provided the result matches, it continues down the chain; if the entire chain is successful, code following do is executed. Provided any result in the chain doesn't match, its value is passed to the else statement. Hence, we can flatten our nested control structures to resemble something more like a pipeline, as well as consolidate redundant error handling in one place.

with also permits evaluation of bare expressions. Let's say we need to perform a calculation on the response of call_outside_server/1 before passing it to write_to_db. Turns out that's easy:

with {:ok, token} <- fetch_token(),
     {:ok, resp} <- call_outside_server(token),
     resp = groom_response(resp),
     {:ok, db_entry} <- write_to_db(resp) do
  db_entry
else
  error -> error
end

Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment