Skip to content

Commit 29f5001

Browse files
committed
update alias-etc
1 parent f9ac9da commit 29f5001

File tree

1 file changed

+151
-92
lines changed

1 file changed

+151
-92
lines changed

13-alias-req-imp.md

Lines changed: 151 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,65 @@
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
1631
defmodule Math do
1732
alias Math.List, as: List
1833
end
19-
```
34+
```
35+
36+
现在,任何对`List`的引用将被自动变成对`Math.List`的引用。
37+
如果还想访问原来的`List`,可以加上它的模块名前缀'Elixir':
2038

21-
现在,任何对```List```的引用将被自动变成对```Math.List```的引用。
22-
如果还想访问原来的```List```,需要前缀'Elixir':
2339
```elixir
2440
List.flatten #=> uses Math.List.flatten
2541
Elixir.List.flatten #=> uses List.flatten
2642
Elixir.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
3552
alias Math.List
3653
```
54+
3755
就相当于:
56+
3857
```elixir
3958
alias Math.List, as: List
4059
```
4160

42-
注意,别名是**词法作用域**。即,你在某个函数中设置别名:
61+
注意,`alias`**词法作用域**。也就是说,当你在某个函数中设置别名:
62+
4363
```elixir
4464
defmodule Math do
4565
def plus(a, b) do
@@ -52,15 +72,17 @@ defmodule Math do
5272
end
5373
end
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
6587
iex> 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)
7092
true
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
85109
iex> import List, only: [duplicate: 2]
86-
nil
110+
List
87111
iex> 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
98123
import Integer, only: :macros
99124
```
125+
100126
或者,仅导入所有的函数:
127+
101128
```elixir
102129
import Integer, only: :functions
103130
```
104131

105-
注意,```import```也是**词法作用域**,意味着我们可以在某特定函数中导入宏或方法:
132+
注意,`import`也遵循**词法作用域**,意味着我们可以在某特定函数定义内导入宏或方法:
133+
106134
```elixir
107135
defmodule Math do
108136
def some_function do
109137
import List, only: [duplicate: 2]
110-
# call duplicate
138+
duplicate(:ok, 10)
111139
end
112140
end
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
125195
iex> is_atom(String)
126196
true
127197
iex> 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
138209
iex> :lists.flatten([1,[2],3])
139210
[1, 2, 3]
140211
```
141212

142213
这也是允许我们动态调用模块函数的机制:
214+
143215
```elixir
144216
iex> mod = :lists
145217
:lists
146218
iex> 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
156230
defmodule Foo do
157231
defmodule Bar do
158232
end
159233
end
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
167241
defmodule Elixir.Foo do
168242
defmodule Elixir.Foo.Bar do
169243
end
170244
alias Elixir.Foo.Bar, as: Bar
171245
end
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

Comments
 (0)