13-別名和代碼引用
=================
[別名](#131-%E5%88%AB%E5%90%8D)
[require](#132-require)
[import](#133-import)
[別名機制](#134-%E5%88%AB%E5%90%8D%E6%9C%BA%E5%88%B6)
[嵌套](#135-%E5%B5%8C%E5%A5%97)
為了實現軟件重用,Elixir提供了三種指令(directives)。
之所以稱之為“指令”,是因為它們的作用域是詞法作用域(lexicla scope)。
## 13.1-別名
宏```alias```可以為任何模塊名設置別名。
想象一下Math模塊,它針對特殊的數學運算使用了特殊的列表實現:
```elixir
defmodule Math do
alias Math.List, as: List
end
```
現在,任何對```List```的引用將被自動變成對```Math.List```的引用。
如果還想訪問原來的```List```,需要前綴'Elixir':
```elixir
List.flatten #=> uses Math.List.flatten
Elixir.List.flatten #=> uses List.flatten
Elixir.Math.List.flatten #=> uses Math.List.flatten
```
>Elixir中定義的所有模塊都在一個主Elixir命名空間。
但是為方便起見,我們平時都不再前面加‘Elixir’。
別名常被使用于定義快捷方式。實際上不帶```as```選項去調用```alias```會
自動將這個別名設置為模塊名的最后一部分:
```elixir
alias Math.List
```
就相當于:
```elixir
alias Math.List, as: List
```
注意,別名是**詞法作用域**。即,你在某個函數中設置別名:
```elixir
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
```
例子中```alias```指令只在函數```plus/2```中有用,```minus/2```不受影響。
## 13.2-require
Elixir提供了許多宏,用于元編程(寫能生成代碼的代碼)。
宏也是一堆代碼,但它在編譯時被展開和執行。
就是說為了使用一個宏,你需要確保它定義的模塊和實現在編譯期時可用。
要使用宏,需要用```require```指令引入定義宏的模塊:
```elixir
iex> Integer.odd?(3)
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.odd?/1
iex> require Integer
nil
iex> Integer.odd?(3)
true
```
Elixir中,```Integer.odd?/1```函數被定義為一個宏,它可以被當作衛兵表達式(guards)使用。
為了調用這個宏,首先得用```require```引用了_Integer_模塊。
總的來說,宏在被用到之前不需要早早被require引用進來,除非我們想讓這個宏在整個模塊中可用。
嘗試調用一個沒有引入的宏會導致報錯。
注意,像```alias```指令一樣,```require```也是詞法作用域的。
下文中我們會進一步討論宏。
## 13.3-import
當想輕松地訪問別的模塊中的函數和宏時,可以使用```import```指令加上宏定義模塊的完整名字。
例如,如果我們想多次使用List模塊中的```duplicate```函數,我們可以這么import它:
```elixir
iex> import List, only: [duplicate: 2]
nil
iex> duplicate :ok, 3
[:ok, :ok, :ok]
```
這個例子中,我們只從List模塊導入了函數```duplicate/2```。
盡管```only:```選項是可選的,但是推薦使用。
除了```only:```選項,還有```except:```選項。
使用選項```only:```,還可以傳遞給它```:macros```或```:functions```。
如下面例子,程序僅導入Integer模塊中所有的宏:
```elixir
import Integer, only: :macros
```
或者,僅導入所有的函數:
```elixir
import Integer, only: :functions
```
注意,```import```也是**詞法作用域**,意味著我們可以在某特定函數中導入宏或方法:
```elixir
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
# call duplicate
end
end
```
在此例子中,導入的函數```List.duplicate/2```只在那個函數中可見。
該模塊的其它函數中都不可用(自然,別的模塊也不受影響)。
注意,若import一個模塊,將自動require它。
## 13.4-別名機制
講到這里你會問,Elixir的別名到底是什么,它是怎么實現的?
Elixir中的別名是以大寫字母開頭的標識符(像String, Keyword等等),在編譯時會被轉換為原子。
例如,別名‘String’會被轉換為```:"Elixir.String"```:
```elixir
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String"
String
```
使用```alias/2```指令,其實只是簡單地改變了這個別名將要轉換的結果。
別名如此這般工作,是因為在Erlang虛擬機中(以及上面的Elixir),模塊名是被表述成原子的。
例如,我們調用一個Erlang模塊的機制是:
```elixir
iex> :lists.flatten([1,[2],3])
[1, 2, 3]
```
這也是允許我們動態調用模塊函數的機制:
```elixir
iex> mod = :lists
:lists
iex> mod.flatten([1,[2],3])
[1,2,3]
```
一句話,我們只是簡單地在原子```:lists```上調用了函數```flatten```。
## 13.5-嵌套
介紹了別名,現在可以講講嵌套(nesting)以及它在Elixir中是如何工作的。
考慮下面的例子:
```elixir
defmodule Foo do
defmodule Bar do
end
end
```
該例子定義了兩個模塊_Foo_和_Foo.Bar_。
后者在_Foo_中可以用_Bar_為名來訪問,因為它們在同一個詞法作用域中。
如果之后開發者決定把Bar模塊挪到另一個文件中,那它就需要以全名(Foo.Bar)或者別名來指代。
換句話說,上面的代碼等同于:
```elixir
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
```
## 13.6-多個
到Elixir v1.2,可以一次使用alias,import,require多個模塊。這在建立Elixir程序的時候很常見,特別是使用嵌套的時候是最有用了。例如,假設你的程序所有模塊都嵌套在```MyApp```下,需要使用別名``` MyApp.Foo```,```MyApp.Bar```和```MyApp.Baz```,可以像下面一次完成:
```elixir
alias MyApp.{Foo, Bar, Baz}
```
## 13.7-```use```
use用于請求在當前上下文中允許使用其他模塊,是一個宏不是指令。開發者頻繁使用```use```宏在當前上下文范圍內引入其他功能,通常是模塊。
例如,在編寫測試時需要使用ExUni框架,開發者需要使用```ExUnit.Case``` 模塊:
```elixir
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
```
在后面,```use```請求需要的模塊,然后調用```__using__/1```回調函數,允許模塊在當前上下文中注入這些代碼。總體說,如下模塊:
```exlixir
defmodule Example do
use Feature, option: :value
end
```
會編譯成
```exlixir
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
```
在以后章節我們可以看到,別名在宏機制中扮演了很重要的角色,來保證宏是干凈的(hygienic)。
討論到這里,模塊基本上講得差不多了。之后會講解模塊的屬性。