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:
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
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 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
: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.
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.
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
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, , 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, , 3]) [1, 2, 3]
We are simply calling the function flatten on the atom
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.