Monday, January 30, 2017

Elixir - Aliases

In order to facilitate software reuse, Elixir provides three directives (alias, require and import) plus a macro called use 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
Now let us look in detail as of what is happening in each of these.

alias

alias 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"))
When running above program, it produces following result:
5
What we have done is give an alias 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 one's 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 wont 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 in built 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 running above program, it produces following result:
5

require

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 its module and implementation are available during compilation. This is done with the require directive
Integer.is_odd(3)
When running above program, it produces 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 so that it can be used as a guard. This means that, in order to invoke Integer.is_odd, we need to first require the Integer module.
When we require Integer and run this again,
require Integer
Integer.is_odd(3)
This time the program will run and produce the output as: true.
In general a module does not need to be 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.

import

We use import whenever we want 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.
import also supports :macros and :functions to be given to :only. For example, to import all macros, one could write:
import Integer, only: :macros
Note that import too is Lexically scoped just like require and alias. Also note that 'import'ing a module also 'require's it.

use

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. Lets see 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
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 call in above situation, code is injected in our local context, where use call was at compile time.

No comments:

Post a Comment