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.