What is an elixir module anyway?

If you programmed anything with Elixir (if not, check our introductory Elixir Interview) one of the first things you encounter are modules. But what exactly is a Module and how do they work internally?

The basics

Modules are one of the key building blocks of the elixir language and also of the BEAM, which is the runtime for elixir code.

Simply put, a module is a collection of functions and attributes.

All code that is executed must be loaded into the runtime at first. The code loading is done on a module level, meaning the runtime can only load modules. Therefore, all elixir code has to reside in a module.

Let’s create an Elixir module

In all examples the interactive elixir shell is used. If you want to follow along start one and type the following.

elixir interactive shell

At first we created a module named MyModule and a function named myFunction that belongs to that module by using demodule and def. The defmodule command returns a tuple with four elements:

  1. the atom :module

  2. name of the module that was defined

  3. a binary representation of the module called object code

  4. the result of the last expression in the do block

Then we called the function myFunction of the MyModule module with the command MyModule.myFunction and got 4711 as return value of the call.

What actually happened here?

The defmodule command did two things.

  1. It compiled our module definition into object code. That object code is a binary representation of the module that can be loaded by the runtime. Beside other things it contains:

    • The name of the module

    • The actual instructions that get executed by the runtime when a function is called. These instructions are called bytecode

    • A lookup table with all function names and a reference on where in the object code to find the function’s bytecode

  2. It loaded the object code into the runtime.

When the MyModule.myFunction() was called the runtime, with the help of the lookup table, found and executed the bytecode for that function. In case of myFunction that bytecode returns the literal 4711 to the caller.

Exploring Elixir object code

Each module in Elixir is compiled seperatly resulting in one binary e.g. object code that includes the actual instructions for the runtime as well as as metadata in the form of module variables (we cover module variables later).

The actual format of the object code does not matter at the moment what’s important is that each module has its own binary, which is object code.

Loading object code

The next example will show how a module can be loaded from object code. Because defmodule will not only create the object code but will also load it into the runtime and i want you to see how the loading happens seperatly, the object code has to be written to a file. To illustrate that MyModule is loaded there is also an additional call to my_function.

elixir module loading

In a newly opened iex MyModule, as expected, is not available. After the object_code was read from the file and loaded with the :code.load_binary function it becomes available.

elixir module loading error

Auto loading object code

In the code loading example the file in which the object code was stored was named my_module_object_code.bin but if you rename that file to my_module.beam it will get loaded by the iex on startup automatically. The .beam filename makes the difference here. Every file that resides inside the directory you are in when executing the iex command and has a name that ends with .beam is loaded on iex startup.

Compiling Elixir files

As you probably know elixir source code is stored in files that have names ending with .ex or sometimes .exs.

Let’s do the same with the MyModule module by creating a file my_module.ex and putting the module definition in it.

defmodule MyModule do
  def my_function do
    4711
  end
end

Next we start a iex inside the same folder where the my_module.ex file is located.
When we try to execute our function like before we just get an error that informs us that the module MyModule is not loaded.

elixir module loading info

The command c("my_module.ex", ".") compiles and the file and loads the module/s defined in it. Furthermore it writes a beam file to the folder provided as second parameter which is the current folder in the example.
elixir module folder
A file with the name Elixir.MyModule.beam was created and sure enough when the iex is restarted the module is available without the need to compile the elixir code again.
elixir module recompile

In practice all this is handled behind the scenes by mix the elixir build tool.

Structuring Elixir code with modules

After considering the compile and runtime aspects of modules let’s explore (source-) code side of things next.

There are many ways you can use modules to structure your code and you can, and probably already have, read about this topic in many books, articles and so on. What i want to do is to show the basic building blocks which can be used to build more complex patterns for structuring code.

Contextualize functions with namespaces

Functions that, on an conceptual level, belong together can be put inside a module that then gives these functions context.

defmodule Greet do
  def speek, do: "Hello"
end

defmodule Farewell do
  def speek, do: "Goodbye"
end

The two speek functions are called with Greet.speek() and Farewill.speek() which gives them either the greet or the farewell context.

Another nice thing you can do, but that we will not discuss deeply here, is the creation of one data structure inside each module. These data structures called structs are often used to define the structure of the data on which the functions of the module operate.

Control the visibility of functions

This one is pretty simple. You can define functions to be callable from everywhere or only from functions that are part of the same module. Public functions are defined with def and private functions are defined with defp.

defmodule Greet do
  # public function
  def speek(name), do: construct_message(name)
  
  # private function
  defp construct_message(name), do: "Hello #{name}!"
end

Inside the iex the callGreet.speek("Hans") will return the expected result, but the call Greet.construct_message("Hans") will result in an exception ** (UndefinedFunctionError) function Greet.construct_message/1 is undefined or private Greet.construct_message("Hans")

Annotate modules

Module attributes are the last thing you can put inside a module beside functions and structs. A module attribute is a key-value pair that attached to a module and can be set, accessed and to a degree modified, at compile time . This can be useful to define static values or pass additional data to the compiler. In this example the module attribute message will be inlined and is available at runtime no more. The moduledoc will be used by the compiler to add documentation to the object code. While the documentation is available at runtime the actual attribute is not.

defmodule Greet do
	# The moduledoc attribute is read by the compiler to generate documentation
	@moduledoc "A module that can greet people."
	
	# The message attribute is used to store a static value which is used in the construct_message function 
	@message "Hello"
	
	def speek(name), do: construct_message(name)
	
	defp construct_message(name), do: "#{@message} #{name}!"
end

To be precise there are module attributes that are also available at runtime and one can create module attributes with that property as well. This is however more cumbersome to write and only used for runtime related stuff. If you are not writing some kind of runtime or compiler you should not care about them and remember that module attributes are a compile time thing.

The Module module

One thing that can be slightly confusing is that elixir provides a module with the name Module. It provides functions and macros for working with modules. For example one can create a module with Module.create/3

Wrapping up

Modules are one of the basic building blocks of elixir. They are not only a language feature but also a basic building block of the BEAM runtime. They are essential for compilation, code loading and execution.
With this knowledge we can dive deeper into what elixir actually is by exploring how it is implemented. But that is for another time.


Über den Autor

Raphael Esterle

Raphael arbeitet als Softwareentwickler und Berater bei inspired. Seine Spezialität ist die IT-Sicherheit in der Software Entwicklung. Bei Konzeption und Implementierung von Software versucht er immer auch den Blick von potenziellen Angreifern einzunehmen. Allgemein beschäftigt er sich mit funktionaler Programmierung seit über fünf Jahren. Elixir ist seit zwei Jahren auf seinem Radar und seitdem in nahezu täglichem Einsatz.

Ihr Problem hätten wir gerne

Lassen Sie uns über Ihre Herausforderungen sprechen. Schicken Sie uns Ihre Projektidee oder Problemstellung, damit wir Ihnen ein detailliertes Feedback über die Entwicklung, die technischen Aspekte und die Verwendung der Programmiersprache Elixir geben können. Schreibe uns jetzt


Paul Anthony, CC BY-SA 4.0, via Wikimedia Commons

Development, Was ist?, ,