Using with in Elixir

Published October 16, 2018 by Toran Billups

In part 22 of my Elixir journey I wanted to share a quick example I put together that helped me wrap my head around `with` in the language.

With

When I first read the Elixir getting started documentation for `with` I learned that it's a common solution to the nested `case` problem I frequently run into. To illustrate this I wrote a program that adds together all the numbers in a list. The catch is we only want to `sum` if that list has no duplicate numbers.

    defmodule MyProgram do
      def calc(list) do
        case duplicate(list) do
          {:ok} ->
            case sum(list) do
              {:ok, total} ->
                IO.puts "total #{total}"
              {:error} ->
                IO.puts "error"
            end
          {:error} ->
            IO.puts "duplicate found!"
        end
      end
    end
  

If you run the program with any duplicates you will see `IO.puts` complaining about a duplicate. If you run the program with unique numbers you will instead see the total.

    MyProgram.calc([1, 1, 2])
  

The power of `with` is that instead of nesting you can chain together a series of match clauses and Elixir will apply something like a circuit break for you. In this scenario, if the `duplicate` function fails to match, Elixir will simply omit execution of the `sum` function altogether.

    defmodule MyProgram do
      def calc(list) do
        with {:ok} <- duplicate(list),
             {:ok, total} <- sum(list) do
          IO.puts "total #{total}"
        else
          {:error} ->
            IO.puts "duplicate found!"
        end
      end
    end
  

The entire module including both `sum` and `duplicate` functions for anyone interested.

    defmodule MyProgram do
      def sum([head | tail]) do
        {:ok, Enum.reduce(tail, head, &(&1 + &2))}
      end
      def duplicate([]), do: {:ok}
      def duplicate([head | tail]) do
        case Enum.any?(tail, &(&1 == head)) do
          true ->
            {:error}
          false ->
            duplicate(tail)
        end
      end
      def calc(list) do
        with {:ok} <- duplicate(list),
             {:ok, total} <- sum(list) do
          IO.puts "total #{total}"
        else
          {:error} ->
            IO.puts "duplicate found!"
        end
      end
    end
  

Buy Me a Coffee

Twitter / Github / Email