Last time we were talking about functions I have mentioned that named functions are living inside modules. Modules provide the namespace for functions and other things defined inside. Usually grouped by same meaning.

For example, Elixir has set of functions to work with enumerables defined in the Enum module. And IO module contains functions for handling Input/Output operations.

We define modules using defmodule keyword followed by its name and a bloc, which contains a body of the module.

defmodule FizzBuzz do
  def print do
    IO.puts "FizzBuzz"
  end
end

To call a function inside module we use ModuleName.function_name format.

iex> FizzBuzz.print
FizzBuzz

We can also define a module inside another module.

defmodule FizzBuzz do
  defmodule MyIO do
    def print(content) do
      IO.puts content
    end
  end

  def print do
    MyIO.print "FizzBuzz"
  end
end

and then call the function using the dot as a separator between module names.

iex> FizzBuzz.MyIO.print "Hello module"
Hello module

That means we can use the same format while we are defining nested module

defmodule FizzBuzz.MyIO do
  def print(content) do
    IO.puts content
  end
end

It is worth to mention that the module name is an atom behind the scenes. For example, if we decide to represent our module name as a string, Elixir would prefix it with “Elixir.”

iex> to_string FizzBuzz.MyIO
"Elixir.FizzBuzz.MyIO"

That means we can use it to call the module in the following way:

iex> :"Elixir.FizzBuzz.MyIO".print "Module name is an atom"
Module name is an atom

import

To eliminate duplication of existing functions we can use import directive to bring functions and macros of one module into our current one. That means we can reuse already defined functions within the current scope.

defmodule FizzBuzz do
  def fizz do
    "Fizz"
  end

  def buzz do
    "buzz"
  end
end

defmodule FizzBuzzUser do
  import FizzBuzz

  def fizz_buzz do
    "#{fizz}#{buzz}"
  end
end

We can see the result by calling our function

iex> FizzBuzzUser.fizz_buzz
"Fizzbuzz"

These functions will not be available to call outside of the module:

iex> FizzBuzzUser.fizz
** (UndefinedFunctionError) function FizzBuzzUser.fizz/0 is undefined or private. Did you mean one of:

      * fizz_buzz/0

    FizzBuzzUser.fizz()

It is allowed to import only (or except) some functions. Using following format:

import ModuleName, only: [function_name: arity, function_name: arity]
import ModuleName, except: [function_name: arity, function_name: arity]

We can also import only functions or only macros:

import ModuleName, only: :functions
import ModuleName, only: :macros

But that works only with only option, except is not allowed in that case.

Using only or except is recommended in order to avoid importing all the functions from that module.

alias

We can use the alias directive to have an alias for a module, usually to minimize typing.

Can be used in the following format:

alias Very.Long.ModuleName, as: ShortName

The option :as can be omitted. In that case, the aliased name would be the same as the last part of the whole module name. From the example above it would be just ModuleName. So the following expressions are equivalent:

alias Very.Long.ModuleName, as: ModuleName
alias Very.Long.ModuleName

If you want to add aliases for the several modules from the same namespace, you can use the following format:

defmodule Very.Long.ModuleName1 do end
defmodule Very.Long.ModuleName2 do end

alias Very.Long.{ModuleName1, ModuleName2}

Let’s summarize it with an example

defmodule VeryLongMethodNameWhichIsAnoyingToType do
  def name do
    "Some name here"
  end
end

defmodule AnotherModule do
  alias VeryLongMethodNameWhichIsAnoyingToType, as: LongName

  def name do
    LongName.name
  end
end
iex> AnotherModule.name
"Some name here"

An alias can be used inside function body in case you don’t want to expose it for the whole module.

There are two more directives require and use. They are more intended to work with macros. I will cover them when I talk about macros.

Module attributes

We can define attributes in the module to serve as annotations and/or constants. They work only on the top level of the module and cannot be set inside a function. To define an attribute we are using attribute name prefixed by @.

defmodule MyApi do
  @version 1

  def api_version do
    @version
  end
end

We can define the same attribute multiple times, but that will affect on functions defined below it.

defmodule MyApi do
  @version 1
  def first_version, do: @version

  @version 2
  def second_version, do: @version
end

MyApi.first_version # => 1
MyApi.second_version # => 2

Summary

Here we covered an introduction to the modules. Now we know how to define them, how to call functions inside the modules. We have also learned how to use directives for code reuse and module attributes. Regarding attributes, there is some interesting usage which I will cover later.