How to pattern match a function in Elixir
I ran into this problem of pattern matching a function when using mox. I wanted to check that a function is injected into another function as a dependency. Here is a simplified example:
test "calls Dependency.do_something with AnotherModule.function/1" do Mox.expect(DependencyMock, :do_something, fn ^(&AnotherModule.function/1) -> nil end) Module.call() Mox.verify!() end
But this code doesn't work in Elixir:
(CompileError) invalid argument for unary operator ^, expected an existing variable, got: ^(&AnotherModule.function/1)
After some research, I found how to do this correctly in Elixir: Improve error messages for invalid expression in match · Issue #5649 · elixir-lang/elixir
x = &List.first/1 case &List.first/1 do ^x -> true end
So my use case only needs a small fix:
test "calls Dependency.do_something with AnotherModule.function/1" do expected_fun = &AnotherModule.function/1 Mox.expect(DependencyMock, :do_something, fn ^expected_fun -> nil end) Module.call() Mox.verify!() end
One caveat is that it doesn't work for anonymous functions even if they have the same body and arity. Because they are completely unrelated and Elixir doesn't know how to check if they can match:
fun1 = fn _ -> nil end fun2 = fn _ -> nil end case fun2 do ^fun1 -> true _ -> false end
false
Pattern matching is a really useful feature in functional languages. But Elixir's implementation does have some unexpected behaviour. For example:
- Pattern match functions like explained above
Pattern match an empty map
%{}
empty = %{} case %{} do ^empty -> true end
true
Pattern match a pid
pid = self() case self() do ^pid -> true end
true
I guess we need to get used to them for quite some time, because as José Valim explained in that issue: "There is very little interest in making functions valid patterns in Elixir."