How Function Sequence in Elixir Can Trip You Up
I've been getting into Elixir over the last year and have loved the experience. One thing that has bitten me is the order in which functions are defined. In most language that makes no difference but for languages that use pattern-matching this makes or breaks things, and further underlines the importance of tests.
Consider the following example where the tests defined in GreeterTest
pass:
defmodule Greeter do
def greet(%{"person" => person, "greeting" => greeting}) do
"#{greeting}, #{person}"
end
def greet(%{"person" => person}) do
"Hello, #{person}"
end
end
defmodule GreeterTest do
use ExUnit.Case
test "specific greeting" do
assert Greeter.greet(%{"person" => "Anne", "greeting" => "Hi"}) == "Hi, Anne"
end
test "generic greeting" do
assert Greeter.greet(%{"person" => "Bill"}) == "Hello, Bill"
end
end
However, if I switch the order of the functions defined in Greeter
, the second test fails:
1) test specific greeting (GreeterTest)
Assertion with == failed
code: assert Greeter.greet(%{"person" => "Anne", "greeting" => "Hi"}) == "Hi, Anne"
left: "Hello, Anne"
right: "Hi, Anne"
That's because Elixir scans the functions from the top and asks for each function, Does the call being made match me?. It finds def greet(%{"person" => person})
and sees that the call parameters %{"person" => "Anne", "greeting" => "Hi"}
match %{"person" => person}
and does not bother checking the next function which is a "better" match since it matches both person
and greeting
.
This is why it's critical that more specific pattern matches should be specified first, with looser ones later. It also underlines the importance of having tests to make refactoring safer as moving functions around a file is pretty common and nobody quite expects any impact from that. Not in Elixir.