axelson

axelson

Scenic Core Team

Elixir Gotchas and Common Issues Wiki

Preamble

This Wiki is intended to be a community-maintained (see the Contribution Guidelines if interested) resource of common “gotchas” (unexpected issues) that beginners to Elixir might experience.

The main intended audience is programmers used to other languages, who come to Elixir and find themselves confused by some particularities of the language and its ecosystem. We also hope it can be used by experienced Elixir programmers as a tool for answering questions and guiding beginners - a quick link to have handy.

Please note that we aren’t aiming to write for beginners to programming. For that, you can check out Joy of Elixir.

We’re also not aiming to replace any official documentation. Beginners to Elixir should start with the Elixir Getting Started guides (which are excellent).

Table of Contents

Gotchas and Common Issues

My list of integers is printing as a string (charlists)

In IEx or the output of IO.inspect/2, if you have a list of integers where all the integers are printable ascii code points, they will be printed as a charlist. For example:

  • [6, 7, 8] is rendered as [6, 7, 8] since 6 is not a printable ascii code point
  • [7, 8, 9] is rendered as '\a\b\t'
    • Both are equivalent: '\a\b\t' == [7, 8, 9]
  • [99, 97, 116] is rendered as 'cat'

It’s important to understand that the list of integers and the charlist are exactly equivalent. There’s no reason to and no way to “convert” between them. But you can get your IEx shell to represent them differently on inspect.

More details

I thought Elixir data was immutable, but I’m allowed to reassign variables

The following is valid Elixir code:

a = 1
a = 3 # Changes the binding of `a`
IO.puts(a) # Prints "3"

While it looks like this Elixir code is mutating data, it is actually rebinding the variable a.

More details

Why do we prepend to lists?

You may have learned other languages, where they build lists by appending, while in Elixir we usually prepend. But why? It’s not just style. In Elixir, and most other languages that use immutable data, prepending to a list is much more efficient.

More details

Under the hood, most implementations of lists boil down to a classic singly linked list. So, appending with mutable data would mean modifying the next-pointer of the previously last node to point at the new last node.

But with immutable data, you can’t do that! So you have to create a new node, pointing to the added node, and throw away the old one. Any existing references to this, must be updated… such as the next-pointer in that previously second-to-last node. However, the same condition applies, recursively, all the way back to the beginning of the list.

So, appending to a list with immutable data means you have to reconstruct the entire list every time. Since this must be done for every node you append, it takes the time to process the whole list from linear (proportional to the length) to quadratic (proportional to the square of the length).

By contrast, if you prepend a node, whether the data is mutable or not, you simply create it pointing to the old first node. Nothing more needs to be done. So, the time to process one node is constant, so the time to process the whole list is once again linear.

I can’t reassign variables inside an if block or a loop

a = 1
if a > 0 do
  a = 3
end
IO.puts(a) # prints "1"

This is related to the previous section. The main takeaway is: rebinding a variable only applies to the current block scope.

More details

Here’s a more realistic example of this gotcha

vegetables = ["radish", "celery", "carrot"]

# We want this to become a list of vegetables starting with the letter "c"
c_vegetables = []

# We'll loop over `vegetables` 
# and try to concat each one that starts with "c" to the above list
Enum.each(vegetables, fn vegetable ->
  if String.starts_with?(vegetable, "c") do
    c_vegetables = Enum.concat(c_vegetables, [vegetable])

    # Inside this `if` block, `c_vegetables` will be rebound to one-item list
    # But it doesn't change the value of `c_vegetables` outside this block
    IO.inspect(c_vegetables) # Prints either `["celery"]` or `["carrot"]`
  end
end)

IO.inspect(c_vegetables) # This still has the value `[]`

Instead you should reassign the value of c_vegetables itself

vegetables = ["radish", "celery", "carrot"]

# Enum.filter docs: https://hexdocs.pm/elixir/Enum.html#filter/2
c_vegetables = Enum.filter(vegetables, fn vegetable ->
  String.starts_with?(vegetable, "c")
end)

IO.inspect(c_vegetables) # Prints out ["celery", "carrot"]
  • To understand what is happening you should understand how scoping works in Elixir.
  • This was actually allowed up to Elixir 1.6(?), and gave a warning up until Elixir 1.9(?)

Further reading:

Examples:

Sometimes people write a def without a do block

i.e. first line of this snippet:

def foo(result, acc)

def foo(:ok, _acc) do
  :ok 
end

def foo(:error, acc) do
  {:error, acc}
end

This is called a function head, or a bodyless function clause. If a function with default values has multiple clauses, it is required to create a function head for declaring defaults.

More details

Further reading:

Examples:

I can’t pattern-match on an empty map

Why does %{} match %{foo: true}? It’s because an empty map matches any map, not just empty maps.

These match successfully:

%{} = %{}
%{} = %{a: 1} # this doesn't error out!

Here’s an example of trying to pattern-match to an empty map in a function declaration.

def my_function(%{}) do
  :unexpected
end

def my_function(map) do
  map
end

 # This is matched by the first function clause above!
my_function(%{a: 1})
# => :unexpected

If you want to match only to specifically an empty map, you’ll either need to use either == %{} or a different guard clause.

More details

If Elixir/Erlang didn’t work this way then whenever you wanted to pattern match a map you would have to provide all of the keys, which would not be very ergonomic.

Instead in some cases you may want to use a guard instead:

def my_function(options) when options == %{} do
  # Handle case when no options are passed
end

Using String.to_atom on untrusted input

It is important to not call String.to_atom/1 on untrusted input from the network or user

More details

The documentation for String.to_atom/1 states:

Warning: this function creates atoms dynamically and atoms are not garbage-collected. Therefore, string should not be an untrusted value, such as input received from a socket or during a web request. Consider using to_existing_atom/1 instead.

Examples:

Similarly you need to ensure that you don’t use :erlang.binary_to_term/2 on untrusted input as well:

https://blog.ispirata.com/how-to-destroy-your-application-using-erlang-binary-to-term-1-575ff7d05333

This is the same reason to generally avoid Jason.decode/2 with [keys: :atoms] and Poison.Parser.parse/2 with %{keys: :atoms!}

Trying to pattern match on a keyword list

Avoid:

def my_function(name: name, age: age) do
  # do something with `name` and `age`
end

my_function(age: 18, name: "John")
# Results in a `FunctionClauseError`
More details

Instead you should use functions like Keyword.get/3 or Keyword.fetch!/2:

def my_function(opts) when is_list(opts) do
  name = Keyword.fetch!(opts, :name)
  age = Keyword.fetch!(opts, :age)
  # Or use `Keyword.get/3` if name or age are optional
end

Or pass options as a map:

def my_function(%{name: name, age: age}) do
  # do something with `name` and `age`
end

By pattern matching on a keyword list, you are stating that only a single ordering is allowed (i.e. that :name must be the very first element in the list in our example)

Contribution Guidelines

Guidelines

The primary target of this wiki post is to provide an easily digestible list of common gotchas or issues that beginners in Elixir often encounter. It is also a central place that people can link to an individual gotcha to help explain a specific issue in depth instead of having to type up a response in every chat thread or forum post.

Each gotcha should be commonly run into, so if it is something that does not come up often it’s best to not include it so that this wiki is easily understandable and not overwhelming to a beginner.

When editing, please try to avoid changing the titles of a gotcha because it will break any links that someone has made to the gotcha. And of course, all of the ElixirForum policies apply as well: https://forum.elixirforum.net/faq

Future ideas for gotchas
Runtime vs Compile time
  • Why init/1 in Plug is not called for each request in the Phoenix?
  • When @foo bar() will be evaluated.
Maps and function calls

Accessing a map value is a function call (Intermediate)

a = %{val: 42}
a.val
# is the same as `a.val()`

a.foo

# is equivalent to

case a do
  %{foo: val} -> val
  mod -> mod.foo()
end

Trying to access a map when it isn’t a map (include?)

Not knowing how to interpret trying to call functions on {:ok, term} tuples

# Suppose that fetch_user/0 is a function that returns: 
#     {:ok, %{name: "john", age: 18}}
# But suppose the programmer is expecting simply %{name: "john", age: 18}
a = fetch_user()
a.age

** (ArgumentError) you attempted to apply :age on {:ok, %{age: 18}}. If you are using apply/3, make sure the module is an atom. If you are using the dot syntax, such as map.field or module.function(), make sure the left side of the dot is an atom or a map
    :erlang.apply({:ok, %{name: "john", age: 18}}, :age, [])

Note: This probably isn’t super important to include because the error message is pretty good now (although it could probably be improved slightly, the error message doesn’t make it clear that you are trying to call a function, ideally it would also talk about how map access is actually a function call)

# On OTP 20 this was even more confusing because of tuple calls
iex(1)> a = {:ok, %{age: 20}}
{:ok, %{age: 20}}
iex(2)> a.age
** (UndefinedFunctionError) function :ok.age/1 is undefined (module :ok is not available)
    :ok.age({:ok, %{age: 20}})
Configuration evaluation time
  • When is config/config.exs evaluated
  • Why I cannot configure :kernel options
    • config/config.exs in development is evaluated after VM is started, so it cannot configure already started applications (like :kernel or :stdlib)
    • config/runtime.exs cannot configure above applications, because of the same reason (unless :reboot_systemd_after_config is set to true)
  • Compile time vs runtime configuration
Type related gotchas
  • true, false, and nil are atoms
  • Module names are atoms Foo == :"[Elixir.Foo](http://elixir.Foo)" (also Foo == Elixir.Foo, the Elixir prefix is to avoid naming conflicts on case insensitive filesystems like default setting of NTFS on Windows and default settings of APFS on macOS)
  • foo == [] can be much faster than length(foo) == 0
  • [] has smaller representation when used with :erlang.term_to_binary/1
`:` vs `=>` in map syntax (may syntactic sugar)

Both are valid, : creates an atom, => can be used for any term

Mixing strings and atoms in maps

Especially a problem in Phoenix controllers, and controller tests

mix deps.update doesn't always update dependencies

If your mix.exs file is restrictive, then mix deps.update will run, but won’t update the dependency that you specified. To fix this you need to update the version requirement of the dependency in mix.exs

Needing a . to call a function in a variable
iex(1)> myadd = fn num -> num + 1 end
#Function<44.79398840/1 in :erl_eval.expr/5>
iex(2)> myadd.(1)
2
Single-line Function syntax
Understanding where Phoenix path helpers come from

By default up to Phoenix 1.4(?) the path helpers were always imported when you run use MyAppWeb

in Phoenix 1.5 the MyAppWeb __using__ macro includes an alias of the MyAppWeb.Router.Helpers module which is an auto-generated module.

= vs <- in with clauses

When using the with statement, if you want the non-matched value to be returned or else clauses to be executed, you need to use <- instead of = when pattern matching. Using = is valid but has a different meaning: the failed pattern match will raise a MatchError.

Most Liked

axelson

axelson

Scenic Core Team

Big thanks to Andy Tran (@a8t) and Łukasz Jan Niemier (@hauleth) who helped create the initial version of this wiki :heart:

15
Post #2
mudasobwa

mudasobwa

Creator of Cure

FWIW, if linking to Stack Overflow is inappropriate, hereby I grant any right to copy-paste my answers from there without a necessity to link and/or somehow refer to it, nor even mention the original source.

I am to explicitly clarify this because there are many answers provided by me, some are very detailed and I hope they are of some value, and I really don’t care about anything but the quality of this wiki. Redundant references would just perplex readers.

10
Post #8
dsdshcym

dsdshcym

any.anything will fail if the key does not exists, while any[:anything] will not, but return nil

Yes, this is true if any is a map, but nil is an atom.

In the nil[:anything] case, nil is a special atom because there is a special case in Access.get for nil:

I believe it was introduced in Allow the access protocol on nil, and always return nil · elixir-lang/elixir@cc68813 · GitHub to allow nested get_in(data, [...]) to return nil if any intermediate step to return nil
This special case is useful when it’s used in get_in/3,
but when you expect something to be a map, and calls map[:anything], it would return nil when map is nil.

It should be a rare case, but it did surprise me once before

dsdshcym

dsdshcym

My personal gotchas list (let me know which ones are good candidates to be added to the wiki):

  1. is_atom(nil) returns true (i.e. nil is an atom)
  2. nil[:anything] returns nil (can be useful sometimes, but most times it would surprise me)
  3. Config.config/{2,3} would run a keyword deep merge
    It surprised me when I set the same config in both config.exs and prod.exs
    For example: config :app, :key, values: [v1: 1] + config :app, :key, values: [v2: 2] = config :app, :key, values: [v1: 1, v2: 2]
  4. ExUnit.run would not load any modules (not even the module it’s intended to test, as ExUnit doesn’t know this intention)
  5. function_exported?/3 won’t load the module
    (leads to different behaviour in test environment and compiled environment due to 4)
Aetherus

Aetherus

Erlang also supports atom literals that can’t be written in the format of [a-z]\w*. Just put whatever you want inside ' ', and you have an atom.

Erlang Elixir Type
foo_bar :foo_bar atom
'foo bar' :"foo bar" atom
"foo bar" 'foo bar' charlist (list of Unicode code points)
<<"foo bar">> "foo bar" binary
'Elixir.Foo.Bar' Foo.Bar
Elixir.Foo.Bar
:"Elixir.Foo.Bar"
atom (usually used as an Elixir module name)
$x ?x integer (Unicode code point)

Where Next?

Popular in Wikis Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
axelson
Preamble This Wiki is intended to be a community-maintained (see the Contribution Guidelines if interested) resource of common “gotchas” ...
New
georgeguimaraes
Hi people, since the new year is coming, I’d like to plan my travels for events in 2017. So, what events (Elixir or FP related) that you...
New
Rich_Morin
I’d like to start a discussion of data serialization formats, in the context of Elixir. The rest of this note is a combination of persona...
New
anildigital
Here is list of plugins for different editors Sublime Text 3 — Elixir.tmbundle - https://github.com/elixir-editors/elixir-tmbundle/#co...
New
blackode
This is a wiki - anyone at Trust Level 1 or higher can help keep it updated. Elixir Pocket Syntax Uncommon Logical stuff of Elixir modul...
New
gmile
:httpc HTTP client is part of Erlang standard library, and as such can be easily used in Elixir code too. One particular advantage of usi...
New
shavit
To transcode the video there is FFMPEG. On Demand When a user uploads a video, the app renames and copy the file to a path, then call F...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
blackode
Hi and Hello Every Elixirian. After Learning the Elixir basics, I struck then. I have no idea of how to put my Elixir Knowledge in pract...
New

Other popular topics Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement