PragTob
Blog Post: 10 Elixir gotchas
Elixir is a great language, but some behavior can be unintuitive, confusing and in the worst case lead to bugs. So, I took a look at 10 Elixir gotchas explaining why they exist and how to avoid them!
Most Liked
sasajuric
Another gotcha with module attributes is that each reference injects the value, which can increase disk & mem usage. For example:
defmodule Foo do
@x Enum.to_list(1..1_000_000)
def foo, do: @x
def bar, do: @x
end
Here we inject two identical large lists in the code, and the resulting file is 6 MB.
In contrast, the following code:
defmodule Foo do
def foo, do: x()
def bar, do: x()
defp x, do: unquote(Enum.to_list(1..1_000_000))
end
produces a 4 MB beam, because the list is injected into the compiled beam only once.
In addition, I consider the 2nd version more flexible, because the “constant” can be provided after the implementation, which I often find better than listing various constants at the top of the file.
It’s also worth mentioning that both versions produce constants, as in terms which are handled in a special way by the runtime. See here for details.
BartOtten
Great post.
One more: is_atom(nil) == true as nil is an atom. However, most people mean a defined atom and not ‘undefined’ nil.
dwark
I got caught off guard by:
[1, 2, 3] -- [1, 2, 3] -- [1, 2, 3]
[1, 2, 3]
After checking, the docs spell it out neatly once I starting looking for an explanation
(-- is right-associative).
BartOtten
Got a new gotcha today
Elixir’s ++/2 operator (list concatenation) has a special clause: If the right-hand side is not a list, the operation returns that right-hand side value instead of raising an error.
Never knew about it. Did bite me. Type checker did not warn that the enumeration would possibly
raise an exception. ![]()
iex(1)> [] ++ nil
nil
iex(2)> [] ++ :foo
:foo
iex(3)> [] ++ "bla"
"bla"
The source in erlang is clear. This is the comment on the nil as right-hand side:
if (is_nil(lhs)) {
/* This is buggy but expected, `[] ++ 'not_a_list'` has always resulted
* in 'not_a_list'. */
return rhs;
Doubt I like the behavior, but alas…there it is.
josevalim
The term ordering is essential for data structures such as maps, sets, ordsets, orddict, and others. Dynamic languages have heterogeneous collections, which means you can have keys, lists, and sets with distinct data types. Some of these data structures are more efficiently implemented by having a total order. If Erlang/Elixir only allowed you to compare the same data types, those data structures would have to define their “total” ordering, and it would ultimately be inefficient, especially for composite data types (e.g. storing structs inside sets, such as a set of dates).
The main source of confusion is because Elixir comparison operators are structural, not semantic. But there are good reasons for those as well. The docs go in detail over this: Kernel — Elixir v1.17.0-dev
The mutability of module attributes shouldn’t be a concern, really. Defining modules in Elixir is mutable a whole. defmodule, def, attrs are all mutable operations that define a module, functions, etc as you execute them. This what allows you, for example, to programmatically define a function. But, as meta-programming, those abilities are only really there at compile-time by design.








