1- 13-别名和代码引用
1+ 13-alias,require和import
22=================
3- [ 别名] ( #131-%E5%88%AB%E5%90%8D )
4- [ require] ( #132-require )
5- [ import] ( #133-import )
6- [ 别名机制] ( #134-%E5%88%AB%E5%90%8D%E6%9C%BA%E5%88%B6 )
7- [ 嵌套] ( #135-%E5%B5%8C%E5%A5%97 )
83
9- 为了实现软件重用,Elixir提供了三种指令(directives)。
10- 之所以称之为“指令”,是因为它们的作用域是词法作用域(lexicla scope)。
4+ 为了实现软件重用,Elixir提供了三种指令(` alias ` ,` require ` 和` import ` ),
5+ 外加一个宏命令` user ` ,如下:
6+
7+ ``` elixir
8+ # Alias the module so it can be called as Bar instead of Foo.Bar
9+ alias Foo .Bar , as: Bar
10+
11+ # Ensure the module is compiled and available (usually for macros)
12+ require Foo
13+
14+ # Import functions from Foo so they can be called without the `Foo.` prefix
15+ import Foo
16+
17+ # Invokes the custom code defined in Foo as an extension point
18+ use Foo
19+ ```
20+
21+ 下面我们将深入细节。记住前三个之所以称之为“指令”,
22+ 是因为它们的作用域是* 词法作用域(lexicla scope)* ,
23+ 而` use ` 是一个普通拓展点(common extension point)。
24+
25+ ## alias
26+
27+ 指令` alias ` 可以为任何模块设置别名。
28+ 想象一下之前使用过的` Math ` 模块,它针对特殊的数学运算提供了特殊的列表(list)实现:
1129
12- ## 13.1-别名
13- 宏``` alias ``` 可以为任何模块名设置别名。
14- 想象一下Math模块,它针对特殊的数学运算使用了特殊的列表实现:
1530``` elixir
1631defmodule Math do
1732 alias Math .List , as: List
1833end
19- ```
34+ ```
35+
36+ 现在,任何对` List ` 的引用将被自动变成对` Math.List ` 的引用。
37+ 如果还想访问原来的` List ` ,可以加上它的模块名前缀'Elixir':
2038
21- 现在,任何对``` List ``` 的引用将被自动变成对``` Math.List ``` 的引用。
22- 如果还想访问原来的``` List ``` ,需要前缀'Elixir':
2339``` elixir
2440List .flatten # => uses Math.List.flatten
2541Elixir .List .flatten # => uses List.flatten
2642Elixir .Math .List .flatten # => uses Math.List.flatten
2743```
2844
29- > Elixir中定义的所有模块都在一个主Elixir命名空间。
30- 但是为方便起见,我们平时都不再前面加‘Elixir’。
45+ > 注意:Elixir中定义的所有模块都被定义在Elixir命名空间内。
46+ 但为方便起见,在引用它们时,你可以省略它们的前缀‘Elixir’。
47+
48+ 别名常被使用于定义快捷方式。实际应用中,不带` :as ` 选项调用` alias ` 会
49+ 自动将别名设置为该模块名称的最后一部分:
3150
32- 别名常被使用于定义快捷方式。实际上不带``` as ``` 选项去调用``` alias ``` 会
33- 自动将这个别名设置为模块名的最后一部分:
3451``` elixir
3552alias Math .List
3653```
54+
3755就相当于:
56+
3857``` elixir
3958alias Math .List , as: List
4059```
4160
42- 注意,别名是** 词法作用域** 。即,你在某个函数中设置别名:
61+ 注意,` alias ` 是** 词法作用域** 。也就是说,当你在某个函数中设置别名:
62+
4363``` elixir
4464defmodule Math do
4565 def plus (a, b) do
@@ -52,15 +72,17 @@ defmodule Math do
5272 end
5373end
5474```
55- 例子中``` alias ``` 指令只在函数``` plus/2 ``` 中有用,``` minus/2 ``` 不受影响。
5675
57- ## 13.2-require
58- Elixir提供了许多宏,用于元编程(写能生成代码的代码)。
76+ 例子中` alias ` 指令设置的别名只在函数` plus/2 ` 中有效,函数` minus/2 ` 则不受影响。
5977
60- 宏也是一堆代码,但它在编译时被展开和执行。
61- 就是说为了使用一个宏,你需要确保它定义的模块和实现在编译期时可用。
78+ ## require
79+
80+ Elixir提供了许多宏用于元编程(可以编写生成代码的代码)。
81+
82+ 宏是在编译时被执行和展开的代码。
83+ 也就是说为了使用宏,你需要确保定义这个宏的模块及实现在你的代码的编译时可用(即被加载)。
84+ 这使用` require ` 指令实现:
6285
63- 要使用宏,需要用``` require ``` 指令引入定义宏的模块:
6486``` elixir
6587iex> Integer .odd? (3 )
6688** (CompileError ) iex: 1 : you must require Integer before invoking the macro Integer .odd? / 1
@@ -70,140 +92,177 @@ iex> Integer.odd?(3)
7092true
7193```
7294
73- Elixir中,``` Integer.odd?/1 ``` 函数被定义为一个宏,它可以被当作卫兵表达式(guards)使用。
74- 为了调用这个宏,首先得用``` require ``` 引用了_Integer_模块。
95+ Elixir中,` Integer.odd?/1 ` 函数被定义为一个宏,因此它可以被当作卫兵表达式(guards)使用。
96+ 为了调用这个宏,首先需要使用` require ` 引用` Integer ` 模块。
97+
98+ 总的来说,一个模块在被用到之前不需要早早地require,除非我们需要用到这个模块中定义的宏的时候。
99+ 尝试调用一个没有加载的宏时,会报出一个异常。
100+ 注意,像` alias ` 指令一样,` require ` 指令也是词法作用域的。
101+ 在后面章节我们会进一步讨论宏。
75102
76- 总的来说,宏在被用到之前不需要早早被require引用进来,除非我们想让这个宏在整个模块中可用。
77- 尝试调用一个没有引入的宏会导致报错。
78- 注意,像 ``` alias ``` 指令一样, ``` require ``` 也是词法作用域的 。
79- 下文中我们会进一步讨论宏。
103+ ## import
104+
105+ 当想轻松地访问模块中的函数和宏时,可以使用 ` import ` 指令避免输入模块的完整名字 。
106+ 例如,如果我们想多次使用 ` List ` 模块中的 ` duplicate/2 ` 函数,我们可以import它:
80107
81- ## 13.3-import
82- 当想轻松地访问别的模块中的函数和宏时,可以使用``` import ``` 指令加上宏定义模块的完整名字。
83- 例如,如果我们想多次使用List模块中的``` duplicate ``` 函数,我们可以这么import它:
84108``` elixir
85109iex> import List , only: [duplicate: 2 ]
86- nil
110+ List
87111iex> duplicate :ok , 3
88112[:ok , :ok , :ok ]
89113```
90114
91- 这个例子中,我们只从List模块导入了函数``` duplicate/2 ``` 。
92- 尽管``` only: ``` 选项是可选的,但是推荐使用。
115+ 这个例子中,我们只从List模块导入了函数` duplicate ` (元数是2的那个)。
116+ 尽管` :only ` 选项是可选的,但是仍推荐使用,以避免向当前命名空间内导入这个模块内定义的所有函数。
117+ 还有` :except ` 选项,可以* 排除* 一些函数而导入其余的。
118+
119+ 还有选项` :only ` ,传递给它` :macros ` 或` :functions ` ,来导入该模块的所有宏或函数。
120+ 如下面例子,程序仅导入` Integer ` 模块中定义的所有的宏:
93121
94- 除了``` only: ``` 选项,还有``` except: ``` 选项。
95- 使用选项``` only: ``` ,还可以传递给它``` :macros ``` 或``` :functions ``` 。
96- 如下面例子,程序仅导入Integer模块中所有的宏:
97122``` elixir
98123import Integer , only: :macros
99124```
125+
100126或者,仅导入所有的函数:
127+
101128``` elixir
102129import Integer , only: :functions
103130```
104131
105- 注意,``` import ``` 也是** 词法作用域** ,意味着我们可以在某特定函数中导入宏或方法:
132+ 注意,` import ` 也遵循** 词法作用域** ,意味着我们可以在某特定函数定义内导入宏或方法:
133+
106134``` elixir
107135defmodule Math do
108136 def some_function do
109137 import List , only: [duplicate: 2 ]
110- # call duplicate
138+ duplicate ( :ok , 10 )
111139 end
112140end
113141```
114- 在此例子中,导入的函数``` List.duplicate/2 ``` 只在那个函数中可见。
115- 该模块的其它函数中都不可用(自然,别的模块也不受影响)。
116142
117- 注意,若import一个模块,将自动require它。
143+ 在这个例子中,导入的函数` List.duplicate/2 ` 只在函数` some_function ` 中可见,
144+ 在该模块的其它函数中都不可用(自然,别的模块也不受影响)。
145+
146+ 注意,若` import ` 一个模块,将自动` require ` 它。
147+
148+ ## use
118149
119- ## 13.4-别名机制
120- 讲到这里你会问,Elixir的别名到底是什么,它是怎么实现的?
150+ 尽管不是一条指令,` use ` 是一个宏,与帮助你在当前上下文中使用模块的` require ` 指令联系紧密。
151+ ` use ` 宏常被用来引入外部的功能到当前的词法作用域---通常是模块。
152+
153+ 例如,在编写测试时,我们使用ExUnit框架。开发者需要使用` ExUnit.Case ` 模块:
154+
155+ ``` elixir
156+ defmodule AssertionTest do
157+ use ExUnit .Case , async: true
158+
159+ test " always pass" do
160+ assert true
161+ end
162+ end
163+ ```
164+
165+ 在代码背后,` use ` 宏先是` require ` 所给的模块,然后在模块上调用` __using__/1 ` 回调函数,
166+ 从而允许这个模块在当前上下文中注入某些代码。
167+
168+ 比如下面这个模块:
169+
170+ ``` exlixir
171+ defmodule Example do
172+ use Feature, option: :value
173+ end
174+ ```
175+
176+ 会被编译成(即宏` use ` 扩展)
177+
178+ ``` exlixir
179+ defmodule Example do
180+ require Feature
181+ Feature.__using__(option: :value)
182+ end
183+ ```
184+
185+ 到这里,关于Elixir的模块基本上讲得差不多了。后面会讲解模块的属性(Attribute)。
186+
187+ ## 别名机制
188+
189+ 讲到这里你会问,Elixir的别名到底是什么,它是怎么实现的?
190+
191+ Elixir中的别名是以大写字母开头的标识符(像` String ` , ` Keyword ` ),在编译时会被转换为原子。
192+ 例如,别名‘String’默认情况下会被转换为原子` :"Elixir.String" ` :
121193
122- Elixir中的别名是以大写字母开头的标识符(像String, Keyword等等),在编译时会被转换为原子。
123- 例如,别名‘String’会被转换为``` :"Elixir.String" ``` :
124194``` elixir
125195iex> is_atom (String )
126196true
127197iex> to_string (String )
128198" Elixir.String"
129- iex> :"Elixir.String"
130- String
199+ iex> :"Elixir.String" == String
200+ true
131201```
132202
133- 使用``` alias/2 ``` 指令,其实只是简单地改变了这个别名将要转换的结果。
203+ 使用` alias/2 ` 指令,其实只是简单地改变了这个别名将要转换的结果。
204+
205+ 别名会被转换为原子,是因为在Erlang虚拟机(以及上面的Elixir)中,模块是由原子表述。
206+ 例如,我们调用一个Erlang模块函数的机制是:
134207
135- 别名如此这般工作,是因为在Erlang虚拟机中(以及上面的Elixir),模块名是被表述成原子的。
136- 例如,我们调用一个Erlang模块的机制是:
137208``` elixir
138209iex> :lists .flatten ([1 ,[2 ],3 ])
139210[1 , 2 , 3 ]
140211```
141212
142213这也是允许我们动态调用模块函数的机制:
214+
143215``` elixir
144216iex> mod = :lists
145217:lists
146218iex> mod.flatten ([1 ,[2 ],3 ])
147219[1 ,2 ,3 ]
148220```
149- 一句话,我们只是简单地在原子``` :lists ``` 上调用了函数``` flatten ``` 。
150221
151- ## 13.5-嵌套
152- 介绍了别名,现在可以讲讲嵌套(nesting)以及它在Elixir中是如何工作的。
222+ 我们只是简单地在原子` :lists ` 上调用了函数` flatten ` 。
223+
224+ ## 模块嵌套
153225
226+ 我们已经介绍了别名,现在可以讲讲嵌套(nesting)以及它在Elixir中是如何工作的。
154227考虑下面的例子:
228+
155229``` elixir
156230defmodule Foo do
157231 defmodule Bar do
158232 end
159233end
160234```
161- 该例子定义了两个模块_Foo_和_Foo.Bar_ 。
162- 后者在_Foo_中可以用_Bar_为名来访问,因为它们在同一个词法作用域中。
163- 如果之后开发者决定把Bar模块挪到另一个文件中,那它就需要以全名(Foo.Bar)或者别名来指代。
164235
165- 换句话说,上面的代码等同于:
236+ 该例子定义了两个模块:` Foo ` 和` Foo.Bar ` 。
237+ 后者在` Foo ` 中可以用` Bar ` 为名来访问,因为它们在同一个词法作用域中。
238+ 上面的代码等同于:
239+
166240``` elixir
167241defmodule Elixir .Foo do
168242 defmodule Elixir .Foo .Bar do
169243 end
170244 alias Elixir .Foo .Bar , as: Bar
171245end
172246```
173- ## 13.6-多个
174- 到Elixir v1.2,可以一次使用alias,import,require多个模块。这在建立Elixir程序的时候很常见,特别是使用嵌套的时候是最有用了。例如,假设你的程序所有模块都嵌套在``` MyApp ``` 下,需要使用别名``` MyApp.Foo ``` ,``` MyApp.Bar ``` 和``` MyApp.Baz ``` ,可以像下面一次完成:
175247
176- ``` elixir
177- alias MyApp .{Foo , Bar , Baz }
178- ```
179- ## 13.7-``` use ```
248+ 如果之后开发者决定把` Bar ` 模块定义挪出` Foo ` 模块的定义,但是在` Foo ` 中仍然使用` Bar ` 来引用,
249+ 那它就需要以全名(Foo.Bar)来命名,或者向上文提到的,在` Foo ` 中设置个别名来指代。
180250
181- use用于请求在当前上下文中允许使用其他模块,是一个宏不是指令。开发者频繁使用``` use ``` 宏在当前上下文范围内引入其他功能,通常是模块。
251+ ** 注意:** 在Elixir中,你并不是必须在定义` Foo.Bar ` 模块之前定义` Foo ` 模块,
252+ 因为编程语言会将所有模块名翻译成原子。
253+ 你可以定义任意嵌套的模块而不需要注意其名称链上的先后顺序
254+ (比如,在定义` Foo.Bar.Baz ` 前不需要提前定义` foo ` 或者` Foo.Bar ` )。
182255
183- 例如,在编写测试时需要使用ExUni框架,开发者需要使用``` ExUnit.Case ``` 模块:
184- ``` elixir
185- defmodule AssertionTest do
186- use ExUnit .Case , async: true
256+ 在后面几章我们会看到,别名在宏里面也扮演着重要角色,来保证它们是“干净”(hygienic)的。
187257
188- test " always pass" do
189- assert true
190- end
191- end
192- ```
193- 在后面,``` use ``` 请求需要的模块,然后调用``` __using__/1 ``` 回调函数,允许模块在当前上下文中注入这些代码。总体说,如下模块:
194- ``` exlixir
195- defmodule Example do
196- use Feature, option: :value
197- end
198- ```
199- 会编译成
200- ``` exlixir
201- defmodule Example do
202- require Feature
203- Feature.__using__(option: :value)
204- end
205- ```
258+ ## 一次、多个
259+
260+ 从Elixir v1.2版本开始,可以一次性使用alias,import,require操作多个模块。
261+ 这在定义和使用嵌套模块的时候非常有用,这也是在构建Elixir程序的常见情形。
206262
207- 在以后章节我们可以看到,别名在宏机制中扮演了很重要的角色,来保证宏是干净的(hygienic)。
263+ 例如,假设你的程序所有模块都嵌套在` MyApp ` 下,
264+ 你可以一次同时给三个模块:` MyApp.Foo ` ,` MyApp.Bar ` 和` MyApp.Baz ` 提供别名:
208265
209- 讨论到这里,模块基本上讲得差不多了。之后会讲解模块的属性。
266+ ``` elixir
267+ alias MyApp .{Foo , Bar , Baz }
268+ ```
0 commit comments