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!
Great post.
One more: is_atom(nil) == true as nil is an atom. However, most people mean a defined atom and not āundefinedā nil.
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.
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
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).
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
Thereās even a handy overview with all operators: Operators reference ā Elixir v1.16.2
I much prefer CompareChain over (Date|DateTime|Time|etc).compare
. Would be nice to have in Kernel
.
JosƩ also tweeted about adding checks around comparing structs to the typesystem additions coming in 1.17:
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.
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 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.
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.
Could you provide an example of a use case with such comparison? I just never seem to have never bumped into this one.