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!

13 Likes

Great post.

One more: is_atom(nil) == true as nil is an atom. However, most people mean a defined atom and not ā€˜undefinedā€™ nil.

7 Likes

Great one! Would have fit well in the current post with all the nil shenanigans but Iā€™ll add it to the list for a follow up!

The chapter How to use constants is a little bit misleading as module a attributes have not been built to be constant in their design, even though we use them for that.

I agree that a new option for Module.register_attribute/3 to enforce it to be constant would be nice, even though I never saw the immediate need for that as if you respect the fact that you define attributes on top before code, this problem goes away.

I mean, the post mentions that they arenā€™t actually constant and more of a work-around but also linking to the docs saying to use them as constants. I donā€™t know how I should make it clearer there while keeping some brevity and not making it the entire topic of the post.

1 Like

Ah my bad, you could also point out that a private function can be used for the same thing.

Already updated with that - or well a public function to keep it accessible. I discussed it a bit with JosĆ©, there are some things that just donā€™t work without module attributes or would require other workarounds.

Related list collected a few years ago on this forum: Elixir Gotchas and Common Issues Wiki

4 Likes

Some great ones in there, thanks!

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).

7 Likes

Oh wow, thatā€™s also news to me! Thanks!

I think the great thing about Elixir is that pretty much all of these are well spelled out in the docs :green_heart:

Thereā€˜s even a handy overview with all operators: Operators reference ā€” Elixir v1.16.2

3 Likes

I much prefer CompareChain over (Date|DateTime|Time|etc).compare. Would be nice to have in Kernel.

2 Likes

JosƩ also tweeted about adding checks around comparing structs to the typesystem additions coming in 1.17:

https://twitter.com/josevalim/status/1785989792141890015

4 Likes

itā€™s really cool and makes me happy to know that I may have contributed in a small way to this (even if itā€™s just re prioritizing it) - blog post is already updated highlighting it! Genuinely one of the best things about elixir: DevX is taken very seriously and often times even issues I just think about are fixed a release or 2 later. The team has a very good and firm understanding of what issues people experience and follow through with fixing them often.

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.

17 Likes

Using module attributes in a 1-1 way that constants are used in other languages is possibly the most prevalent anti-pattern I come across that actually causes negative side-effects. Well, based on my very small sample size, at least :slight_smile: While a lot of has been done to cut down on what is considered a compile time dependency, liberally referencing other modules in attributes was a major source of long compile times at a previous job.

2 Likes

Wow, I wouldnā€™t have expected that! Thanks for sharing. It makes sense when you think about how its probably resolved at the erlang level (i.e. since they may change, just insert their current value and not have it be a reference) but thatā€™s surprising for sure! Thanks SaÅ”a!

Oh, and another gotcha! Even after understanding comparisons, people (like past me) still forget that true, false, and nil are just atoms so comparing them is done so alphabetically.

3 Likes

Could you provide an example of a use case with such comparison? I just never seem to have never bumped into this one.