Avoiding nested case in Elixir

Published October 15, 2018 by Toran Billups

In part 21 of my Elixir journey I wanted to improve a messy `case` example with help from pattern matching.

Nested Case?

In my previous post I did a comparison of `case` and `cond` by writing a simple program that would evaluate if a number was even or odd. If the number was even it would also check to see if that number was greater than 10.

    defmodule MyProgram do
      def even(n), do: rem(n, 2) == 0
      def gt(n, max), do: n > max
      def calc([]), do: []
      def calc([head | tail]) do
        IO.puts "calculation for #{head}"
        case even(head) do
          true ->
            case gt(head, 10) do
              true ->
                IO.puts "even and > 10"
              false ->
                IO.puts "even and < 10"
            end
          false ->
            IO.puts "odd number"
        end
        calc(tail)
      end
    end
  

The program would take a list of numbers as input and simply print out the result using `IO.puts`

    MyProgram.calc([2, 16, 3])
  

The first implementation seems like the wrong use of `case` altogether because of the nesting required to solve this problem. My next thought was to reach for pattern matching as a means to improve clarity for the reader.

    defmodule MyProgram do
      def even(n), do: rem(n, 2) == 0
      def gt(n, max), do: n > max
      def go(true, true), do: IO.puts "even and > 10"
      def go(true, false), do: IO.puts "even and < 10"
      def go(false, _), do: IO.puts "odd number"
      def calc([]), do: []
      def calc([head | tail]) do
        IO.puts "calculation for #{head}"
        go(even(head), gt(head, 10))
        calc(tail)
      end
    end
  

While pattern matching did eliminate the nesting problem, having a function with 2 unlabeled boolean values didn't feel like an improvement in readability. I started searching for a trick that would allow me to pattern match with named arguments or something equivalent.

    defmodule MyProgram do
      def even(n), do: rem(n, 2) == 0
      def gt(n, max), do: n > max
      def go(%{ even: true, gt: true }), do: IO.puts "even and > 10"
      def go(%{ even: true, gt: false }), do: IO.puts "even and < 10"
      def go(%{ even: false, gt: _ }), do: IO.puts "odd number"
      def calc([]), do: []
      def calc([head | tail]) do
        IO.puts "calculation for #{head}"
        go(%{ even: even(head), gt: gt(head, 10) })
        calc(tail)
      end
    end
  

I found that using the map data structure would allow me to pattern match against the key name. I'll continue to explore the problem space that plagues me with nested `case` as a path to learning more about control flow in the language.


Buy Me a Coffee

Twitter / Github / Email