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!

2 Likes

On Elixir forum there are few topics about those and other gotchas already:

Regarding your list:

  1. Elixir developers by default are diabetics. syntax sugar or pretty printing is a common thing. Over time you would find it rather handy than confusing. You just need to get used to sugar. When you learn a language you should not compare it to others. If doing so I as an Elixir developer would say that mutable variables are confusing. Are they really? In many cases especially sharing mutable data between processes leads to terrible things that are often hard to track well. However some gotchas are actually gotchas only for people who didn’t spend their time to read a syntax reference and other official guides. Simply you are going to be surprised expecting English characters in Chinese language.

  2. charlist is not just an old concept, but a way to handle same thing with other data type.

iex> list = ~c"a"
iex> <<List.first(list)>>
"a"

Because of different type processing and updating data is entirely different, so in some cases you can use one or another to speed up your code. For those reasons we have something in between which is called iolist:

  1. At start it’s confusing, but only a bit. Soon people ask themselves how they could live without that. I can’t imagine that I would pattern-match dozens of keys including all possible cases where some keys may not exist - all of that just to pattern-match on one or few keys.

  2. I don’t remember if I ever did a pattern-match on range, so that example surprised me for a 1ms. Anyway I prefer strict things and I’m always using a struct module when doing pattern-matching on structs. Therefore it was never confusing for me at all.

  3. That’s a good point. It was a good decision at first, but then people started to work on polymorphic data and the concept has been messed up. Without that rarely calling stuff like Map.get/2 would not be a pain at all.

  4. Who said that keywords are the only way for options? As same as charlist vs string topic. Sometimes we use keyword lists and sometimes we use maps. Again expecting lists to behave like maps is weird and shows that someone did not understand why the keyword lists are used for.

  5. That’s only problem if you do not write much guards. It’s funny to see a fully documented function which does not checks if integer parameter is actually an Integer. No matter if we talk about function guards or a type system if they would not be used then the bugs would not be discovered. While I’m aware what bugs could happen I never had such a case personally. I don’t write much guards and documentation only in small pet projects that I’m working on locally.

  6. Not sure how about you, but I know about comparison operators only from used them on integers and floats. Maybe in some languages they are more widely used for other types as well, but I think that I never assumed that such operators would work on date/time structs. I know I’m weird, my first Linux distribution was Gentoo, so maybe I like to go the hard way and I don’t have problem with that. For me it’s rather more like I don’t assume that some behaviour is implemented before I would not read it from documentation. One time I did that with module attributes as I expected them to be equivalent ones for Ruby’s attributes. Once I “learned” to read documentation properly I started to have less problems.

  7. For me this is the biggest gotcha from your list. The most surprising is not this itself, but in context with a . (dot) operator, as it raises KeyError when you try to fetch same thing with this operator, see:

iex> nil.something
** (KeyError) key :something not found in: nil

If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
    iex:1: (file)
iex> nil[:something]
nil
  1. Module attributes are of course important part of Elixir, but you would see their value much more when you would learn meta-programming. When do so you would never expect a warning on “redefining” a module attribute. :smiley:

Summary

Gotchas for most people are things that do not work they way they want. In many cases reading documentation would not cause confusion. After some practice they can’t be called weird, but just different from concepts in other languages. That’s not bad thing. I would even say that’s expected to happen. If every language would work the same way as others we would use just one language. :joy:

Don’t assume something before you understand it and it’s context. Simply by following this rule you shouldn’t have bigger problems with most languages especially the modern, high-level ones. :+1:

1 Like

Thanks for the links, I must say I didn’t review a lot before but went off what I’ve seen people learning Elixir struggle with in real life.

I get your point about “if you read the docs, it shouldn’t be surprising and you shouldn’t expect it to work in any way”. That’s not how most people work though (imo), they’ve learned something in another programming language and it’s difficult to completely switch that part of esp. when learning. Particularly there are some things where I’d say elixir & erlang behaves extra weird.

Yup I know, but I do think bringing up that goes beyond the scope of the blog post

Never suggested that. I think how maps are matched is great and of course there should be no other way. It makes sense when you think about how their matching works, but still people sometimes expect %{} to match the empty map.

It’s not the only way, but it’s the default way that most Elixir functions use. The preference is also discussed in the discussion I linked Passing in options: Maps vs. Keyword lists - Chat / Discussions - Elixir Programming Language Forum The point isn’t about expecting them to work like maps, but that they are the default (in Elixir, Erlang switched over) and are less handy (imo) to handle this use case.

The problem here is that it “works” it just does something unexpected which isn’t exactly a great DevX. Also lots of Elixir programmers are coming from Ruby (like myself) and there this works - I don’t know about other languages but I’ve seen folks from different programming backgrounds be stung by this.

Why is that surprising? To me this isn’t surprising at all as that’s how . works - it raises if the key isn’t there and the key isn’t there.

I don’t expect a warning there - it’s just unexpected that our workaround for constants can actually change its value during the course of a module which isn’t really… constant. I know why it is and how it’s used, doesn’t make that property less confusing for new comers or potentially dangerous for a “constant” use case especially in huge modules (which you shouldn’t have, but many people do) easily producing odd bugs.

1 Like

I would take that as compliment. :smiley:

Yeah most, so there are also those using maps. Lists are faster, especially that Elixir maps key and value into {key, value} tuple when traversing maps which is a noticeable performance issue. Nobody says a word that PostgreSQL is the default database in phx.new generator. For most use cases it’s more than enough. For others you are free to use other databases. Flexibility is one of the key features of Elixir. I don’t see a point where you saying that something is “default” when you can use something else. Nobody forces you to use keyword lists. As in most cases in Elixir you use specific features when you need them. If it’s terrible to handle in your use case then don’t do that. “Most” of functions you are talking about does not force you to use keyword lists in your functions. :thinking:

Well … in same way of thinking I could say something like:

I become a developer, because I was thinking that I would have a well paid and easy job.

Now 99.(999999999)% of companies gives terrible employee experience, because their jobs are not “easy”, but instead they have a terrible requirement called “thinking”. :joy:

I understand that people “assumes” and prefers “the easy way”, but that would not change a thing. If you don’t learn you would not learn. There is no “easy way” to get rid of all gotchas defined with such a way of thinking.

The key point in being a good developer is intuition. The rest is a matter of time i.e. learning and practice. There is no place for assumptions.

Yes and that’s normal. The surprising part is inconsistency between . (dot) notation and [] (square brackets) notation. While map[:not_existing_key] == nil makes sense, nil[:not_existing_key] == nil does not. Of course both notations are different as dot one is strict requirement and square brackets notation is an optional requirement, but for nil literal and maybe also for a map with specific not existing key there should be at least warning about nil source. Looks like that Access.get/3 is responsible for that and it has a pattern-matching on nil container. For me it’s confusing why there is no IO.warn/1 call or something like that. It’s about adding just one line.

# …
    Attempted function clauses (showing 6 out of 6):
    
        def get(%module{} = container, key, default)
        def get(map, key, default) when is_map(map)
        def get(list, key, default) when is_list(list) and is_atom(key)
        def get(list, key, _default) when is_list(list) and is_integer(key)
        def get(list, key, _default) when is_list(list)
        def get(nil, _key, default)
# …

I was driving a car, but decided to ride a horse. I was completely shocked that it gets tired and need to rest, that’s … that’s … insane!

:joy:

There were a couple who assumed that everyone on the earth is full of peace like them and in order to prove that they were travelling the entire world.
Spoiler alert: they have died. :man_facepalming:

People would never learn even if learning would be forced, even if government would sponsor books or even if there would be free libraries. They would always “know better”, make false assumptions and pay a penalty for that. Everyone is responsible for our own assumptions. You would never had enough money and time to met everyone’s expectations. :see_no_evil:

I would even say that most gotchas I heard about are actually gotchas for me in terms of way of thinking. :joy:

Think about it like that … there were, are or would be aliens - in more or less “advanced” form of live. Statistically there is a chance that we would find them or they would find us. Now think that aliens assumes that we know their language, their technology, their way of thinking and so on … How are you going to prepare humanity for such scenario?

In case you say that unlike aliens we know programming languages. Yes, we know. There is around 70 “major” languages, hundreds of less popular ones and even thousands if you count the experiments or those made just for fun. Now design a new language that would have unique features and make sure that none of them would be confusing for new developers willing to use it without even reading documentation … :page_facing_up:

Even if you would limit to said 70 mostly used languages in production there is no way to not make everything obvious especially that some people may want to use your new language as their first one. The “hard way” is definitely “next level” as you have to create a language that needs to be easy to understand for a Whitespace developer. :rofl:

From above all gotchas like “it works different in other language” have completely no sense. The true gotchas are potential inconsistency or unexpected behaviour when following the language concept.

Spot on!

1 Like

I am trying to relearn Elixir, after learning it a few years ago. This will be helpful. Thanks.

1 Like

Thanks for the Feedback, that’s what it’s there for!

@Eiji Your argument seems to base around “Well if you know everything, then it all makes sense” and sure enough that’s true. People don’t know everything, esp. when they are out there learning new things - I think pointing them to common “aha” moments is very worthwhile.

I also think there are certain designs or details in programming languages that are confusing and more error prone than others. What are the real use cases that I can compare a list and a PID and get a return value based upon a semi-arbitrary order as if this was good/legal instead of an exception? Why are true and false atoms and not… booleans? If they are just atoms why are they true and not :true? There are reasons, and I’m sure I’m missing something on the term ordering. But it can be hard to adapt to and facilitates causing subtle bugs.

Expecting perfection and complete knowledge from people also isn’t the way to go. People are human. People make mistakes. Thankfully Elixir, Phoenix & co. realize this and try to be helpful to the programmer pointing them in the right direction.

For sure they do. Once José Valim answered my question about module attributes I realised that I could find the answer myself easily in the documentation and for a person like me it was a bit shame. That was why when answering question on Elixir forum I have tried to link as many resources as possible like documentation for functions/macros I used, guides and articles related to the question.

By experience I learned to assume as less as possible (if any). After “newbie” questions like that I have learned Elixir and all other things differently. That actually speed up learning a lot and I got my first Elixir job after 3 months of learning it - it was short, but well paid, so that was some kind of achievement. What I’m trying to say is that there is no problem what gotchas people list, but the problem is the terrible behaviour “forcing them” to assume things.

Elixir is different in many fields:

  1. All people who want to learn Elixir are kind and that’s one of the best things comparing to other languages. There is no language that have any “feature” that is so far way comparing to other languages.
  2. When working on Elixir apps developers does not “assume” that Elixir is best for everything and that’s why we have Ports, BIFs, NIFs and so on …
  3. The language itself is constructed in way to easily extend the whole ecosystem instead of forcing many restrictions.

That’s why such behaviours are completely wrong:

  1. Toxic job offers going even more far away than “FullStack Engineer” concept or people who not try to learn Elixir, but are “rewriting” everything they touch to make things work as close as possible to their previous language
  2. Assuming things without learning basics - pay attention that some gotchas shows no knowledge of syntax which is absolutely basic knowledge and something that is excellently documented
  3. Trying to overuse their favourite language to make things work where they do not fit the concepts of language

It’s not even about programming language, human language or any other specific thing. We need to progress by learning. Nobody excepts someone from buying and reading tons of books, but it’s wrong to not have even said “5 minutes” to read about Elixir’s syntax.

That would be funny if it would not be real.

Just think that you expect that someone would make you a native Chinese speaker, but at the start you note that you are not interested in tones, so you expect to speak Chinese without speaking Chinese. :crazy_face:

The reason behind many so-called “gotchas” is not even that something is uncommon or different comparing to something else. It’s about people are too lazy to read what the core concepts are. It’s like:

Oh, I just watched an advertisement of some drink and it sounds like it would be best in my case as I really need sugar!

and “complaining” that they have a healthcare problems. In US there was a law case that forced shops selling hot drinks (coffee, tea, etc.) to include a sentence on every cup that the drink is hot and you need to be careful drinking it. :man_facepalming:


The only exception from “newbies questions” that should be solved by simply reading documentation is that sometimes people have trouble picking up a correct key words when searching what they want. Again, that’s why I was adding helpful resources section to my answers. However now the only good argument for it is already dead as the AI is more or less “smart”, but it’s able to find many things by their description which search engines are terrible with. That’s also the reason after recent StackOverflow reports about lower activity.

So people are confused, but they prefer to ask AI much more than human, so it’s good now, right?

The core problem is that it’s actually the worst solution. While I have some “standard” format in my answers AI’s are just supposed to return answers directly. Some of course links some resources, but because people are lazy most of them are most probably not checking it. The key point is lazy word as in this route we are going straight to one of the worst AI scenarios where some kind of “world end” would happen no matter if AI would become independent and decide to attack people in any way.

No matter what we talk about, gotchas, question/answer stats on some sites or other things - if the reason behind them are lazy people then that’s a serious problem that we can’t simply ignore. As said that has nothing to what we call gotchas, but why we call them. What’s a difference between stealing and buying things? Paying the money! Similar case we have here … It’s small, but terribly important difference. That difference makes me think that some gotchas (about most basic stuff) for me are not actually gotchas, but a serious problems of understanding some “common knowledge” (just like hot drinks being hot).


Maybe I’m bad in explaining things, so I have asked an AI to name what I’m trying to say and it’s answer seems to be correct:

Question: How’s called a term which describes a behaviour of assuming something to be different than they actually are. For example when you don’t know basics of one language and you assumes that they are similar or same to the one you know?

Answer: The term you’re referring to is “false analogy.” A false analogy occurs when someone assumes that two things are similar or the same based on a superficial or incorrect comparison. This can happen in various contexts, including language learning, where someone might assume that the grammar or vocabulary of one language is similar to another because they share a common root or have similar-sounding words. This assumption can lead to confusion and miscommunication because the actual similarities or differences between the languages are not accurately perceived.

That’s interesting and, but I have tried few other questions to understand better the differences between so-called false analogy and gotchas. Here are some interesting parts:

(…)
a “false analogy” is a logical fallacy where similarities between two situations are incorrectly assumed, leading to incorrect conclusions. In contrast, “gotchas” are practical challenges or misunderstandings encountered by learners
(…)
a false analogy is a misleading comparison that simplifies the learning process, potentially leading to a superficial understanding of programming. Gotchas, on the other hand, are specific, often subtle issues within a programming language that can cause problems for beginners.
(…)


Hope that the explanation of false analogy makes more sense for you. Have a great weekend!

To be honest, I didn’t go through the whole list or read the details under each item, but I think some of the “gotchas” are actually features.

Pretty much all of them are features or rooted in features. The point is that the behavior can be confusing and lead to errors or at least confusion - hence the “gotcha” The ways in which that happens, would be explained in the text beneath :sweat_smile:

1 Like