1
- 13-别名和代码引用
1
+ 13-alias,require和import
2
2
=================
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 )
8
3
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)实现:
11
29
12
- ## 13.1-别名
13
- 宏``` alias ``` 可以为任何模块名设置别名。
14
- 想象一下Math模块,它针对特殊的数学运算使用了特殊的列表实现:
15
30
``` elixir
16
31
defmodule Math do
17
32
alias Math .List , as: List
18
33
end
19
- ```
34
+ ```
35
+
36
+ 现在,任何对` List ` 的引用将被自动变成对` Math.List ` 的引用。
37
+ 如果还想访问原来的` List ` ,可以加上它的模块名前缀'Elixir':
20
38
21
- 现在,任何对``` List ``` 的引用将被自动变成对``` Math.List ``` 的引用。
22
- 如果还想访问原来的``` List ``` ,需要前缀'Elixir':
23
39
``` elixir
24
40
List .flatten # => uses Math.List.flatten
25
41
Elixir .List .flatten # => uses List.flatten
26
42
Elixir .Math .List .flatten # => uses Math.List.flatten
27
43
```
28
44
29
- > Elixir中定义的所有模块都在一个主Elixir命名空间。
30
- 但是为方便起见,我们平时都不再前面加‘Elixir’。
45
+ > 注意:Elixir中定义的所有模块都被定义在Elixir命名空间内。
46
+ 但为方便起见,在引用它们时,你可以省略它们的前缀‘Elixir’。
47
+
48
+ 别名常被使用于定义快捷方式。实际应用中,不带` :as ` 选项调用` alias ` 会
49
+ 自动将别名设置为该模块名称的最后一部分:
31
50
32
- 别名常被使用于定义快捷方式。实际上不带``` as ``` 选项去调用``` alias ``` 会
33
- 自动将这个别名设置为模块名的最后一部分:
34
51
``` elixir
35
52
alias Math .List
36
53
```
54
+
37
55
就相当于:
56
+
38
57
``` elixir
39
58
alias Math .List , as: List
40
59
```
41
60
42
- 注意,别名是** 词法作用域** 。即,你在某个函数中设置别名:
61
+ 注意,` alias ` 是** 词法作用域** 。也就是说,当你在某个函数中设置别名:
62
+
43
63
``` elixir
44
64
defmodule Math do
45
65
def plus (a, b) do
@@ -52,15 +72,17 @@ defmodule Math do
52
72
end
53
73
end
54
74
```
55
- 例子中``` alias ``` 指令只在函数``` plus/2 ``` 中有用,``` minus/2 ``` 不受影响。
56
75
57
- ## 13.2-require
58
- Elixir提供了许多宏,用于元编程(写能生成代码的代码)。
76
+ 例子中` alias ` 指令设置的别名只在函数` plus/2 ` 中有效,函数` minus/2 ` 则不受影响。
59
77
60
- 宏也是一堆代码,但它在编译时被展开和执行。
61
- 就是说为了使用一个宏,你需要确保它定义的模块和实现在编译期时可用。
78
+ ## require
79
+
80
+ Elixir提供了许多宏用于元编程(可以编写生成代码的代码)。
81
+
82
+ 宏是在编译时被执行和展开的代码。
83
+ 也就是说为了使用宏,你需要确保定义这个宏的模块及实现在你的代码的编译时可用(即被加载)。
84
+ 这使用` require ` 指令实现:
62
85
63
- 要使用宏,需要用``` require ``` 指令引入定义宏的模块:
64
86
``` elixir
65
87
iex> Integer .odd? (3 )
66
88
** (CompileError ) iex: 1 : you must require Integer before invoking the macro Integer .odd? / 1
@@ -70,140 +92,177 @@ iex> Integer.odd?(3)
70
92
true
71
93
```
72
94
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
+ 在后面章节我们会进一步讨论宏。
75
102
76
- 总的来说,宏在被用到之前不需要早早被require引用进来,除非我们想让这个宏在整个模块中可用。
77
- 尝试调用一个没有引入的宏会导致报错。
78
- 注意,像 ``` alias ``` 指令一样, ``` require ``` 也是词法作用域的 。
79
- 下文中我们会进一步讨论宏。
103
+ ## import
104
+
105
+ 当想轻松地访问模块中的函数和宏时,可以使用 ` import ` 指令避免输入模块的完整名字 。
106
+ 例如,如果我们想多次使用 ` List ` 模块中的 ` duplicate/2 ` 函数,我们可以import它:
80
107
81
- ## 13.3-import
82
- 当想轻松地访问别的模块中的函数和宏时,可以使用``` import ``` 指令加上宏定义模块的完整名字。
83
- 例如,如果我们想多次使用List模块中的``` duplicate ``` 函数,我们可以这么import它:
84
108
``` elixir
85
109
iex> import List , only: [duplicate: 2 ]
86
- nil
110
+ List
87
111
iex> duplicate :ok , 3
88
112
[:ok , :ok , :ok ]
89
113
```
90
114
91
- 这个例子中,我们只从List模块导入了函数``` duplicate/2 ``` 。
92
- 尽管``` only: ``` 选项是可选的,但是推荐使用。
115
+ 这个例子中,我们只从List模块导入了函数` duplicate ` (元数是2的那个)。
116
+ 尽管` :only ` 选项是可选的,但是仍推荐使用,以避免向当前命名空间内导入这个模块内定义的所有函数。
117
+ 还有` :except ` 选项,可以* 排除* 一些函数而导入其余的。
118
+
119
+ 还有选项` :only ` ,传递给它` :macros ` 或` :functions ` ,来导入该模块的所有宏或函数。
120
+ 如下面例子,程序仅导入` Integer ` 模块中定义的所有的宏:
93
121
94
- 除了``` only: ``` 选项,还有``` except: ``` 选项。
95
- 使用选项``` only: ``` ,还可以传递给它``` :macros ``` 或``` :functions ``` 。
96
- 如下面例子,程序仅导入Integer模块中所有的宏:
97
122
``` elixir
98
123
import Integer , only: :macros
99
124
```
125
+
100
126
或者,仅导入所有的函数:
127
+
101
128
``` elixir
102
129
import Integer , only: :functions
103
130
```
104
131
105
- 注意,``` import ``` 也是** 词法作用域** ,意味着我们可以在某特定函数中导入宏或方法:
132
+ 注意,` import ` 也遵循** 词法作用域** ,意味着我们可以在某特定函数定义内导入宏或方法:
133
+
106
134
``` elixir
107
135
defmodule Math do
108
136
def some_function do
109
137
import List , only: [duplicate: 2 ]
110
- # call duplicate
138
+ duplicate ( :ok , 10 )
111
139
end
112
140
end
113
141
```
114
- 在此例子中,导入的函数``` List.duplicate/2 ``` 只在那个函数中可见。
115
- 该模块的其它函数中都不可用(自然,别的模块也不受影响)。
116
142
117
- 注意,若import一个模块,将自动require它。
143
+ 在这个例子中,导入的函数` List.duplicate/2 ` 只在函数` some_function ` 中可见,
144
+ 在该模块的其它函数中都不可用(自然,别的模块也不受影响)。
145
+
146
+ 注意,若` import ` 一个模块,将自动` require ` 它。
147
+
148
+ ## use
118
149
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" ` :
121
193
122
- Elixir中的别名是以大写字母开头的标识符(像String, Keyword等等),在编译时会被转换为原子。
123
- 例如,别名‘String’会被转换为``` :"Elixir.String" ``` :
124
194
``` elixir
125
195
iex> is_atom (String )
126
196
true
127
197
iex> to_string (String )
128
198
" Elixir.String"
129
- iex> :"Elixir.String"
130
- String
199
+ iex> :"Elixir.String" == String
200
+ true
131
201
```
132
202
133
- 使用``` alias/2 ``` 指令,其实只是简单地改变了这个别名将要转换的结果。
203
+ 使用` alias/2 ` 指令,其实只是简单地改变了这个别名将要转换的结果。
204
+
205
+ 别名会被转换为原子,是因为在Erlang虚拟机(以及上面的Elixir)中,模块是由原子表述。
206
+ 例如,我们调用一个Erlang模块函数的机制是:
134
207
135
- 别名如此这般工作,是因为在Erlang虚拟机中(以及上面的Elixir),模块名是被表述成原子的。
136
- 例如,我们调用一个Erlang模块的机制是:
137
208
``` elixir
138
209
iex> :lists .flatten ([1 ,[2 ],3 ])
139
210
[1 , 2 , 3 ]
140
211
```
141
212
142
213
这也是允许我们动态调用模块函数的机制:
214
+
143
215
``` elixir
144
216
iex> mod = :lists
145
217
:lists
146
218
iex> mod.flatten ([1 ,[2 ],3 ])
147
219
[1 ,2 ,3 ]
148
220
```
149
- 一句话,我们只是简单地在原子``` :lists ``` 上调用了函数``` flatten ``` 。
150
221
151
- ## 13.5-嵌套
152
- 介绍了别名,现在可以讲讲嵌套(nesting)以及它在Elixir中是如何工作的。
222
+ 我们只是简单地在原子` :lists ` 上调用了函数` flatten ` 。
223
+
224
+ ## 模块嵌套
153
225
226
+ 我们已经介绍了别名,现在可以讲讲嵌套(nesting)以及它在Elixir中是如何工作的。
154
227
考虑下面的例子:
228
+
155
229
``` elixir
156
230
defmodule Foo do
157
231
defmodule Bar do
158
232
end
159
233
end
160
234
```
161
- 该例子定义了两个模块_Foo_和_Foo.Bar_ 。
162
- 后者在_Foo_中可以用_Bar_为名来访问,因为它们在同一个词法作用域中。
163
- 如果之后开发者决定把Bar模块挪到另一个文件中,那它就需要以全名(Foo.Bar)或者别名来指代。
164
235
165
- 换句话说,上面的代码等同于:
236
+ 该例子定义了两个模块:` Foo ` 和` Foo.Bar ` 。
237
+ 后者在` Foo ` 中可以用` Bar ` 为名来访问,因为它们在同一个词法作用域中。
238
+ 上面的代码等同于:
239
+
166
240
``` elixir
167
241
defmodule Elixir .Foo do
168
242
defmodule Elixir .Foo .Bar do
169
243
end
170
244
alias Elixir .Foo .Bar , as: Bar
171
245
end
172
246
```
173
- ## 13.6-多个
174
- 到Elixir v1.2,可以一次使用alias,import,require多个模块。这在建立Elixir程序的时候很常见,特别是使用嵌套的时候是最有用了。例如,假设你的程序所有模块都嵌套在``` MyApp ``` 下,需要使用别名``` MyApp.Foo ``` ,``` MyApp.Bar ``` 和``` MyApp.Baz ``` ,可以像下面一次完成:
175
247
176
- ``` elixir
177
- alias MyApp .{Foo , Bar , Baz }
178
- ```
179
- ## 13.7-``` use ```
248
+ 如果之后开发者决定把` Bar ` 模块定义挪出` Foo ` 模块的定义,但是在` Foo ` 中仍然使用` Bar ` 来引用,
249
+ 那它就需要以全名(Foo.Bar)来命名,或者向上文提到的,在` Foo ` 中设置个别名来指代。
180
250
181
- use用于请求在当前上下文中允许使用其他模块,是一个宏不是指令。开发者频繁使用``` use ``` 宏在当前上下文范围内引入其他功能,通常是模块。
251
+ ** 注意:** 在Elixir中,你并不是必须在定义` Foo.Bar ` 模块之前定义` Foo ` 模块,
252
+ 因为编程语言会将所有模块名翻译成原子。
253
+ 你可以定义任意嵌套的模块而不需要注意其名称链上的先后顺序
254
+ (比如,在定义` Foo.Bar.Baz ` 前不需要提前定义` foo ` 或者` Foo.Bar ` )。
182
255
183
- 例如,在编写测试时需要使用ExUni框架,开发者需要使用``` ExUnit.Case ``` 模块:
184
- ``` elixir
185
- defmodule AssertionTest do
186
- use ExUnit .Case , async: true
256
+ 在后面几章我们会看到,别名在宏里面也扮演着重要角色,来保证它们是“干净”(hygienic)的。
187
257
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程序的常见情形。
206
262
207
- 在以后章节我们可以看到,别名在宏机制中扮演了很重要的角色,来保证宏是干净的(hygienic)。
263
+ 例如,假设你的程序所有模块都嵌套在` MyApp ` 下,
264
+ 你可以一次同时给三个模块:` MyApp.Foo ` ,` MyApp.Bar ` 和` MyApp.Baz ` 提供别名:
208
265
209
- 讨论到这里,模块基本上讲得差不多了。之后会讲解模块的属性。
266
+ ``` elixir
267
+ alias MyApp .{Foo , Bar , Baz }
268
+ ```
0 commit comments