|
1 | 1 | --- |
2 | 2 | layout: getting_started |
3 | | -title: 10 Recursion |
| 3 | +title: 10 alias, require and import |
4 | 4 | guide: 10 |
5 | 5 | --- |
6 | 6 |
|
7 | 7 | # {{page.title }} |
8 | 8 |
|
9 | | -Due to immutability, loops in Elixir (and in functional programming languages) are written differently from conventional imperative languages. For example, in an imperative language, one would write: |
| 9 | +In order to facilitate software reuse, Elixir provides three directives. As we are going to see below, they are called directives because they have **lexical scope**. |
10 | 10 |
|
11 | | -```c |
12 | | -for(i = 0; i < array.length; i++){ |
13 | | - array[i] = array[i] * 2 |
14 | | -} |
| 11 | +### 10.1 alias |
| 12 | + |
| 13 | +`alias` allows you to setup aliases for any given module name. Imagine our `Math` module uses a special list implementation for doing math specific operations: |
| 14 | + |
| 15 | +```elixir |
| 16 | +defmoduleMathdo |
| 17 | +aliasMath.List, as:List |
| 18 | +end |
| 19 | +``` |
| 20 | + |
| 21 | +From now on, any reference to `List` will automatically expand to `Math.List`. In case one wants to access the original `List`, it can be done by accessing the module via `Elixir`: |
| 22 | + |
| 23 | +```elixir |
| 24 | +List.flatten#=> uses Math.List.flatten |
| 25 | +Elixir.List.flatten#=> uses List.flatten |
| 26 | +Elixir.Math.List.flatten#=> uses Math.List.flatten |
| 27 | +``` |
| 28 | + |
| 29 | +> Note: All modules defined in Elixir are defined inside a main Elixir namespace. However, for convenience, you can omit "Elixir." when referencing them. |
| 30 | +
|
| 31 | +Aliases are frequently used to define shortcuts. In fact, calling `alias` without an `as` option sets the alias automatically to the last part of the module name, for example: |
| 32 | + |
| 33 | +```elixir |
| 34 | +aliasMath.List |
15 | 35 | ``` |
16 | 36 |
|
17 | | -In the example above we are mutating the array which is not possible in Elixir. Instead functional languages rely on recursion: a function is called recursively until a condition is reached. Consider the example below that manually sums all the items in the list: |
| 37 | +Is the same as: |
| 38 | + |
| 39 | +```elixir |
| 40 | +aliasMath.List, as:List |
| 41 | +``` |
| 42 | + |
| 43 | +Note that `alias` is **lexically scoped**, which allows you to set aliases inside specific functions: |
18 | 44 |
|
19 | 45 | ```elixir |
20 | 46 | defmoduleMathdo |
21 | | -defsum_list([h|t], acc) do |
22 | | -sum_list(t, h + acc) |
| 47 | +defplus(a, b) do |
| 48 | +aliasMath.List |
| 49 | +# ... |
23 | 50 | end |
24 | 51 |
|
25 | | -defsum_list([], acc) do |
26 | | -acc |
| 52 | +defminus(a, b) do |
| 53 | +# ... |
27 | 54 | end |
28 | 55 | end |
| 56 | +``` |
| 57 | + |
| 58 | +In the example above, since we are invoking `alias` inside the function `plus/2`, the alias will just be valid inside the function `plus/2`. `minus/2` won't be affected at all. |
29 | 59 |
|
30 | | -Math.sum_list([1, 2, 3], 0) #=> 6 |
| 60 | +### 10.2 require |
| 61 | + |
| 62 | +Elixir provides macros as a mechanism for meta-programming (writing code that generates code). |
| 63 | + |
| 64 | +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: |
| 65 | + |
| 66 | +```iex |
| 67 | +iex> Integer.odd?(3) |
| 68 | +** (CompileError) iex:1: you must require Integer before invoking the macro Integer.odd?/1 |
| 69 | +iex> require Integer |
| 70 | +nil |
| 71 | +iex> Integer.odd?(3) |
| 72 | +true |
| 73 | +``` |
| 74 | + |
| 75 | +In Elixir, `Integer.odd?/1` is defined as a macro so it can be used as guards. This means that, in order to invoke `Integer.odd?/1`, we need to first require the `Integer` module. |
| 76 | + |
| 77 | +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. |
| 78 | + |
| 79 | +### 10.3 import |
| 80 | + |
| 81 | +We use `import` whenever we want to easily access functions or macros from other modules without using the qualified name. For instance, if we want to use the `duplicate` function from `List` several times, we can simply import it: |
| 82 | + |
| 83 | +```iex |
| 84 | +iex> import List, only: [duplicate: 2] |
| 85 | +nil |
| 86 | +iex> duplicate :ok, 3 |
| 87 | +[:ok, :ok, :ok] |
31 | 88 | ``` |
32 | 89 |
|
33 | | -In the example above, we invoke `sum_list` giving a list `[1,2,3]` and the initial value `0` as arguments. When a function has many clauses, we will try each clause until we find one that matches according to the pattern matching rules. In this case, the list `[1,2,3]` matches against `[h|t]` which assigns `h = 1` and `t = [2,3]` while `acc` is set to `0`. |
| 90 | +In this case, we are importing only the function `duplicate` (with arity 2) from `List`. Although `only:` is optional, its usage is recommended. `except` could also be given as an option. |
34 | 91 |
|
35 | | -Then, we add the head of the list to the accumulator `h + acc` and call `sum_list` again, recursively, passing the tail of the list as argument. The tail will once again match `[h|t]` until the list is empty, as seen below: |
| 92 | +`import` also supports `:macros` and `:functions` to be given to `:only`. For example, to import all macros, one could write: |
36 | 93 |
|
37 | 94 | ```elixir |
38 | | -sum_list [1, 2, 3], 0 |
39 | | -sum_list [2, 3], 1 |
40 | | -sum_list [3], 3 |
41 | | -sum_list [], 6 |
| 95 | +importInteger, only::macros |
42 | 96 | ``` |
43 | 97 |
|
44 | | -When the list is empty, it will match the final clause which returns the final result of `6`. In imperative languages, such implementation would usually fail for large lists because the stack (in which our execution path is kept) would grow until it reaches a limit. Elixir, however, does tail call optimization in which the stack does not grow when a function exits by calling another function. |
| 98 | +Or to import all functions, you could write: |
45 | 99 |
|
46 | | -Recursion and tail call optimization are an important part of Elixir and are commonly used to create loops, especially in cases where processes need to wait and respond to messages. However, recursion as above is rarely used to manipulate lists, since [the `Enum` module](/docs/stable/Enum.html) already provides many conveniences for working not only with lists but different kinds of Enumerables. For instance, the example above could be simply written as: |
| 100 | +```elixir |
| 101 | +importInteger, only::functions |
| 102 | +``` |
| 103 | + |
| 104 | +Note that `import` is also **lexically scoped**, this means we can import specific macros inside specific functions: |
| 105 | + |
| 106 | +```elixir |
| 107 | +defmoduleMathdo |
| 108 | +defsome_functiondo |
| 109 | +importList, only: [duplicate:2] |
| 110 | +# call duplicate |
| 111 | +end |
| 112 | +end |
| 113 | +``` |
| 114 | + |
| 115 | +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). |
| 116 | + |
| 117 | +Note that importing a module automatically requires it. |
| 118 | + |
| 119 | +## 10.4 Aliases |
| 120 | + |
| 121 | +At this point, you may be wondering, what exactly an Elixir alias is and how it is represented? |
| 122 | + |
| 123 | +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"`: |
47 | 124 |
|
48 | 125 | ```iex |
49 | | -iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end) |
50 | | -6 |
| 126 | +iex> is_atom(String) |
| 127 | +true |
| 128 | +iex> to_string(String) |
| 129 | +"Elixir.String" |
| 130 | +iex> :"Elixir.String" |
| 131 | +String |
51 | 132 | ``` |
52 | 133 |
|
53 | | -Or using the capture syntax: |
| 134 | +By using the `alias/2` directive, we are simply changing what an alias translates to. |
| 135 | + |
| 136 | +Aliases work as described because in the Erlang VM (and consequently Elixir), modules are represented by atoms. For example, that's the mechanism we use to call Erlang modules: |
54 | 137 |
|
55 | 138 | ```iex |
56 | | -iex> Enum.reduce([1, 2, 3], 0, &+/2) |
57 | | -6 |
| 139 | +iex> :lists.flatten([1,[2],3]) |
| 140 | +[1, 2, 3] |
58 | 141 | ``` |
59 | 142 |
|
60 | | -Or finally the `sum/1`function: |
| 143 | +This is also the mechanism that allows us to dynamically call a given function in a module: |
61 | 144 |
|
62 | 145 | ```iex |
63 | | -iex> Enum.sum([1, 2, 3]) |
64 | | -6 |
| 146 | +iex> mod = :lists |
| 147 | +:lists |
| 148 | +iex> mod.flatten([1,[2],3]) |
| 149 | +[1,2,3] |
| 150 | +``` |
| 151 | + |
| 152 | +In other words, we are simply calling the function `flatten` on the atom `:lists`. |
| 153 | + |
| 154 | +## 10.5 Nesting |
| 155 | + |
| 156 | +Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example: |
| 157 | + |
| 158 | +```elixir |
| 159 | +defmoduleFoodo |
| 160 | +defmoduleBardo |
| 161 | +end |
| 162 | +end |
| 163 | +``` |
| 164 | + |
| 165 | +The example above will define two modules `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope. If later the developer decides to move `Bar` to another file, it will need to be referenced by its full name (`Foo.Bar`) or an alias needs to be set using the `alias` directive discussed above. |
| 166 | + |
| 167 | +In other words, the code above is exactly the same as: |
| 168 | + |
| 169 | +```elixir |
| 170 | +defmoduleElixir.Foodo |
| 171 | +defmoduleElixir.Foo.Bardo |
| 172 | +end |
| 173 | +aliasElixir.Foo.Bar, as:Bar |
| 174 | +end |
65 | 175 | ``` |
66 | 176 |
|
67 | | -We will learn more about Enumerables in the "Enumerables and streams" chapter. |
| 177 | +As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic. With this we are almost finishing our tour about Elixir modules, the last topic to cover is module attributes. |
0 commit comments