Pattern matching in function heads: don’t go overboard

Elixir’s pattern matching is great, but make sure it’s not reducing your code’s readability. In particular, just because you can match all the variables you’ll need from within the function’s head doesn’t mean you should.

Take this code:

  def handle_call(:checkout, %{workers: [h | t], monitors: monitors}) do
    # ...
  end

  def handle_call(:checkout, %{workers: [], idle_overflow: [h | t]}) do
    # ...
  end

  def handle_call(:checkout, %{workers: [], idle_overflow: [],
      overflow: overflow, overflow_max: max, worker_sup: sup,
      spec: spec, monitors: monitors})
      when overflow < max do
    # ...
  end

  def handle_call(:checkout, %{workers: [], idle_overflow: [],
      overflow: overflow, overflow_max: max, waiting: waiting}) do
    # ...
  end

A lot of matching is going on there, but how much of it is to differentiate the function heads, and how much is convenience (i.e. binding for later use)?

By leaving only the matches that differentiate the heads and moving other bindings to the function bodies, readability can be improved (although, sadly, this is poorly demonstrated due to the line wrapping required by this blog format):

def handle_call(:checkout, %{workers: [h | t]} = state) do
    %{monitors: monitors} = state
    # ...
  end

  def handle_call(:checkout, %{workers: [], idle_overflow: [h | t]}) do
    # ...
  end

  def handle_call(:checkout, %{workers: [], idle_overflow: [],
      overflow: overflow, overflow_max: max} = state) when overflow < max do
    %{worker_sup: sup, spec: spec, monitors: monitors} = state
    # ...
  end

  def handle_call(:checkout, state) do
    %{workers: [], idle_overflow: [], overflow: overflow,
        overflow_max: max, waiting: waiting} = state
    # ...
  end

Of course, I’m not saying that you should always split matching/binding between function head and body. But if you find yourself binding so many variables in your function heads that your code’s readability suffers, take a good look at them. The bindings that aren’t discriminating can most likely be moved to the function’s body, simplifying your code.

Update June 28th, 2018: It turns out José also uses this technique:

I tend to use this rule: if the key is necessary when matching the pattern, keep it in the pattern, otherwise move it to the body. So I end-up with code like this:

def some_fun(%{field1: :value} = struct) do
  %{field2: value2, field3: value3} = struct
  ...
end
This entry was posted in Elixir, Pattern snippets. Bookmark the permalink.