While experimenting in IEx, I stumbled upon a confusing error that didn’t make sense at first. I wrote what I thought was harmless code:
case %{} do
x when x == %{} -> true
_x -> false
end
And then boom
** (ErlangError) erlang error: :guard_expr
At first, I assumed I messed up my syntax. But no this error was deeper. So I decided to dig in and what I found taught me something fundamental about how Elixir guards work.
Why This Error Happens
The root cause is surprisingly simple:
Elixir guards only allow a limited set of expressions.x == %{}
inside a when
clause is NOT allowed, because you can’t compare against a map in guards.
Guards only allow comparisons with simple values like x == 1
or x == nil
, but map literals (%{}
) and complex data types are forbidden.
So Elixir raises:
erlang error: :guard_expr
→ “Invalid expression inside a guard.”
Correct Version
Instead of forcing %{}
inside a guard, I can directly match it as a pattern:
case %{} do
%{} -> true
_ -> false
end
Why does this work
Because pattern matching is allowed, even with complex structures like maps.
But guards are more strict, and %{}
is not allowed inside them.
My Next Experiment A Custom ||
Macro
I wanted to be clever and build my own version of the ||
operator one where empty lists ([]
), empty tuples ({}
), and empty maps (%{}
) also behaved like false.
So I tried:
defmodule Or do
defmacro left || right do
quote do
case unquote(left) do
x when x in [false, nil] or x == [] or x == {} or x == %{} ->
unquote(right)
x ->
x
end
end
end
end
Guess what? SAME ERROR. Removing or x == %{}
suddenly made it work.
At that point, I knew %{}
is illegal inside guards. Period.
Final Fix My Working Custom ||
Macro
The solution? Avoid guards entirely. Use raw pattern matching instead.
defmodule Or do
defmacro left || right do
quote do
case unquote(left) do
false -> unquote(right)
nil -> unquote(right)
[] -> unquote(right)
{} -> unquote(right)
%{} -> unquote(right)
x -> x
end
end
end
end
Now it behaves almost exactly like ||
, except with extra falsy values:
import Or
[] || "fallback" #=> "fallback"
%{} || "default" #=> "default"
5 || "ignored" #=> 5
Practice Challenges
Try extending your skills:
- Write a
truthy?/1
function that returnstrue
orfalse
based on custom falsy rules (false
,nil
,[]
,{}
,%{}
). - Modify the macro to also treat
""
(empty string) as falsey. - Write an
&&
macro equivalent that mirrors the same logic.