require vz import vz use vz alias directives in Elixir

One of an essential feature of a language when developing software is to reuse the code or the modules.

What are directives ?

So directives are a way via which we can reuse our code, import it and use it with other modules.

What are the available directives in Elixir

When it comes to elixir, we are given 4 options or directives:

  • require
  • import
  • alias
  • use
Require directive

We will be using macros across our code and there is a high chance that we would need to share these macros too.

So let's say we have a module named Foo and we have a macro a_macro

Now, in order to use that macro, we need to guarantee its module and implementation are available during compilation. This is done with the require directive.

iex> Foo.a_macro(1)
** (CompileError) iex:1: you must require Foo before invoking the macro Foo.a_macro/1
iex> require Foo
nil
iex> Foo.a_macro(1)
true

An attempt to call a macro that was not loaded will raise an error.

Import directive

import directive is used for easily accessing functions or macros from other modules without using the fully-qualified name. For instance, if we want to use the duplicate/2 function from the List module several times, we can simply import it:

iex> import List, only: [duplicate: 2]
nil
iex> duplicate :ok, 3
[:ok, :ok, :ok]

In this case, we are importing only the function duplicate (with arity 2) from List. Although :only is optional, its usage is recommended in order to avoid importing all the functions of a given module inside the namespace. :except could also be given as an option in order to import everything in a module except a list of functions.

import also supports :macros and :functions to be given to :only. For example, to import all macros, one could write:

import Integer, only: :macros

Or to import all functions, you could write:

import Integer, only: :functions

Note that import is lexically scoped too. This means that we can import specific macros or functions inside function definitions:

defmodule Math do
  def some_function do
    import List, only: [duplicate: 2]
    duplicate(:ok, 10)
  end
end

In the example above, the imported List.duplicate/2 is only visible within that specific function. duplicate/2 won’t be available in any other function in that module (or any other module for that matter).

Note that importing a module automatically requires it.

use directive

Although not a directive, use is a macro tightly related to require that allows you to use a module in the current context. The use macro is frequently used by developers to bring external functionality into the current lexical scope, often modules.

For example, in order to write tests using the ExUnit framework, a developer should use the ExUnit.Case module:

defmodule AssertionTest do
  use ExUnit.Case, async: true

  test "always pass" do
    assert true
  end
end

Behind the scenes, use requires the given module and then calls the using/1 callback on it allowing the module to inject some code into the current context. Generally speaking, the following module:

defmodule Example do
  use Feature, option: :value
end
is compiled into

defmodule Example do
  require Feature
  Feature.__using__(option: :value)
end

With this we are almost finishing our tour about Elixir modules. The last topic to cover is module attributes.

Aliases directive

At this point you may be wondering: what exactly an Elixir alias is and how is it represented?

An alias in Elixir is a capitalized identifier (like String, Keyword, etc) which is converted to an atom during compilation. For instance, the String alias translates by default to the atom :"Elixir.String":

iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true

By using the alias/2 directive, we are simply changing the atom the alias expands to.

Aliases expand to atoms because in the Erlang VM (and consequently Elixir) modules are always represented by atoms. For example, that’s the mechanism we use to call Erlang modules:

iex> :lists.flatten([1, [2], 3])
[1, 2, 3]

This is also the mechanism that allows us to dynamically call a given function in a module:

iex> mod = :lists
:lists
iex> mod.flatten([1, [2], 3])
[1, 2, 3]

We are simply calling the function flatten on the atom :lists.

Summary

alias to more conveniently reference modules

#Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar

require to ensure macros are available and compiled

#Ensure the module is compiled and available (usually for macros)
require Foo

import to more conveniently reference functions

# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo

use to give module opportunity to "hook" into your code

# Invokes the custom code defined in Foo as an extension point
use Foo

and all directives are lexically scoped.