Why do Erlang modules look like atoms in Elixir?

This is a post about how Elixir knows the difference between Erlang modules, Elixir modules, and Elixir atoms

If you have ever come across Elixir code that calls an Erlang module, you will know it looks something like this:

IEx screenshot using an Erlang module

However, normal atoms that are declared in Elixir code don’t get functions, so how does Elixir know the difference between them?

If we open up IEx and create some atoms, there appears to be no difference between the :os atom and the :tyler atom. Yet when we try to call the system_time/0 function on the :os atom it works, but trying to call any kind of function on the :tyler atom raises an error because there is no :tyler module.

IEx screenshot experimenting with atoms

So how does Elixir know when we are dealing with an atom and when we are dealing with an Erlang module?

There are a whole bunch of IEx helpers that are useful for exploring these kinds of things, and the one that will help us right now is the i/1 function, which will print information about the data type of the value we pass it.

So lets test that out on the :tyler atom and the :os atom. Starting with the :tyler atom, we can see we get some information about its value:

IEx screen shot examing the :tyler atom

If we do the same for the :os atom we get a lot more information, and its easy to see that Elixir knows that we are now dealing with a module and not just a regular atom:

IEx screenshot examining the :os atom

If you compare the Reference modules fields for each, you can see that :tyler is just an Atom while :os is Module, Atom. This is not surprising; we already said that :os is an atom and a module, while :tyler is just a regular atom.

Lets try the same thing for the String Elixir module:

IEx screenshot examining the String module

Obviously we know that String is a module, so we are not surprised to see the module-related stuff in there that looks similar to the previous example with :os. What is interesting here is the Reference modules still says Module and Atom.

If we look back at the screenshot above, we can see that the Raw representation field event tells exactly how the module name is represented as an atom in Elixir. Lets play around with that:

IEx screenshot demonstrating that module names are strings

Neat! This demonstrates that String and :"Elixir.String" are equivalent! If you define a module in your code by doing defmodule MyModule or defmodule Foo.Bar then you would be able to access it in the same way, :"Elixir.MyModule and :"Elixir.Foo.Bar" respectively.

By the way: the reason you need the quotes "" in :"Elixir.String" atom is because without them the dot . would be ambigious. You need to use quotes when creating an atom that has any special characters.

This also shows that Elixir keeps track of Erlang vs Elixir modules by using the Elixir prefix in the module name (you can scroll back up and see that was not present for :os). In fact, all Pascal-case “names” Elixir are just syntactic sugar for Elixir-prefixed atoms.

Wrapping up, the key take aways are that:

  • All modules are atoms (:os , String , MyModule )
  • Not all atoms are modules (:tyler)
  • Erlang and Elixir modules are differentiated by the Elixir prefix that is present for Elixir modules