In order to facilitate software reuse, Elixir provides three directives – alias, require and import. It also provides a macro called use which is summarized below −
# Alias the module so it can be called as Bar instead of Foo.Bar alias Foo.Bar, as: Bar # Ensure the module is compiled and available (usually for macros) require Foo # Import functions from Foo so they can be called without the `Foo.` prefix import Foo # Invokes the custom code defined in Foo as an extension point use Foo
Let us now understand in detail about each directive.
The alias directive allows you to set up aliases for any given module name. For example, if you want to give an alias 'Str' to the String module, you can simply write −
alias String, as: Str IO.puts(Str.length("Hello"))
The above program generates the following result −
5
An alias is given to the String module as Str. Now when we call any function using the Str literal, it actually references to the String module. This is very helpful when we use very long module names and want to substitute those with shorter ones in the current scope.
NOTE − Aliases MUST start with a capital letter.
Aliases are valid only within the lexical scope they are called in. For example, if you have 2 modules in a file and make an alias within one of the modules, that alias will not be accessible in the second module.
If you give the name of an in built module, like String or Tuple, as an alias to some other module, to access the inbuilt module, you will need to prepend it with "Elixir.". For example,
alias List, as: String #Now when we use String we are actually using List. #To use the string module: IO.puts(Elixir.String.length("Hello"))
When the above program is run, it generates the following result −
5
Elixir provides macros as a mechanism for meta-programming (writing code that generates code).
Macros are chunks of code that are executed and expanded at compilation time. This means, in order to use a macro, we need to guarantee that its module and implementation are available during compilation. This is done with the require directive.
Integer.is_odd(3)
When the above program is run, it will generate the following result −
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
In Elixir, Integer.is_odd is defined as a macro. This macro can be used as a guard. This means that, in order to invoke Integer.is_odd, we will need the Integer module.
Use the require Integer function and run the program as shown below.
require Integer Integer.is_odd(3)
This time the program will run and produce the output as: true.
In general, a module is not required before usage, except if we want to use the macros available in that module. An attempt to call a macro that was not loaded will raise an error. Note that like the alias directive, require is also lexically scoped. We will talk more about macros in a later chapter.
We use the import directive to easily access functions or macros from other modules without using the fully-qualified name. For instance, if we want to use the duplicate function from the List module several times, we can simply import it.
import List, only: [duplicate: 2]
In this case, we are importing only the function duplicate (with argument list length 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.
The import directive also supports :macros and :functions to be given to :only. For example, to import all macros, a user can write −
import Integer, only: :macros
Note that import too is Lexically scoped just like the require and the alias directives. Also note that 'import'ing a module also 'require's 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. Let us understand the use directive through an example −
defmodule Example do use Feature, option: :value end
Use is a macro that transforms the above into −
defmodule Example do require Feature Feature.__using__(option: :value) end
The use Module first requires the module and then calls the __using__ macro on Module. Elixir has great metaprogramming capabilities and it has macros to generate code at compile time. The __using__ macro is called in the above instance, and the code is injected into our local context. The local context is where the use macro was called at the time of compilation.