Elixir入门教程-alias、require和import

  1. alias
  2. require
  3. import
  4. use
  5. 理解alias
  6. 模块嵌套
  7. 多重 alias/import/require/use

为了便于软件重用,Elixir提供了三个指令(alias、require和import)外加一个叫做use的宏。如下所示:

1
2
3
4
5
6
7
8
9
10
11
# 别名一个模块则它可以被叫做Bar而不是Foo.Bar
alias Foo.Bar, as: Bar
# 确认这个模块是编译好的并且可用的(常常使用于宏)
require Foo
# 从Foo导入函数,所以它们被调用可以不需要 Foo. 这个前缀
import Foo
# 调用Foo里作为一个扩展点定义的代码。
use Foo

我们现在将详细地探讨它们。记住前三个被叫做指令因为它们有作用范围,而use是一个常见的扩展点。

alias

alias允许你给任何给定的模块设置别名。

假设一个模块使用 Math.List 里实现的一个特殊列表。alias 指令允许在模块定义里只使用List来指代Math.List:

1
2
3
4
defmodule Stats do
alias Math.List, as: List
# 在模块的后续的定义里,List将展开为Math.List。
end

原来的List依然可以通过完全限定名Elixir.List在模块Stats里访问。

注意:定义在Elixir里所有的模块定义在Elixir命名空间里。但是,为了方便,当引用它们的时候你可以省略“Elixir.”。

别名常被用来定义快捷方式。实际上,调用 alias 而不用 :as 选项,则自动将别名设置为模块名的最后一部分,例如:

1
alias Math.List

和如下的代码是一样的效果:

1
alias Math.List, as: List

注意:alias 是有作用范围的,它允许你在特定的函数里设置别名:

1
2
3
4
5
6
7
8
9
10
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end

上面的例子里,因为我们在 plus/2 里调用 alias ,别名将只在 plus/2 里有效,minus/2 将一点也不受影响。

require

Elixir提供宏作为元编程(写代码来生成代码)的一种机制。

宏是一段代码,它在代码编译的时候被执行和展开。这意味着,为了使用宏,我们需要保证在编译期间它的模块和实现是可用的。这可以用 require 指令来完成:

1
2
3
4
5
6
iex> Integer.is_odd(3)
** (UndefinedFunctionError) function Integer.is_odd/1 is undefined or private. However there is a macro with the same name and arity. Be sure to require Integer if you intend to invoke this macro
iex> require Integer
Integer
iex> Integer.is_odd(3)
true

在Elixir里,Integer.is_odd/1 被定义成为一个宏,所以它可以被用来作为一个卫语句。也就是说,为了调用 Integer.is_odd/1 ,我们首先需要 require Integer模块。

总而言之,一个模块在使用前是不需要被 require 的,除非是我们想要使得模块里的宏可用。尝试调用一个没有被装载的宏将引起一个错误。注意,像 alias 指令,require 也是有作用范围的。我们将在后面的章节更深入地讨论宏。

import

在任何时候我们想不用完全限定名称而方便地访问其他模块的函数或宏,我们就用 import 。例如,如果我们想用 List 里的 duplicate/2 几次,我们可以 import 它:

1
2
3
4
iex> import List, only: [duplicate: 2]
List
iex> duplicate :ok, 3
[:ok, :ok, :ok]

在这个例子里,我们仅从List里导入函数duplicate(只有两个入参)。虽然 :only 是可选的,但是为了避免将给定模块的所有函数都导入到命名空间里,这个用法是被推荐使用的。:except 也可以作为选项被设置,它的作用是为了导入一个模块里 除了 一些函数外其他所有函数。

import 也支持把 :macros 和 :functions 给 :only 选项。例如,导入模块的所有宏,可以这么写:

1
import Integer, only: :macros

或者导入模块的所有函数,你可以这么写:

1
import Integer, only: :functions

注意:import也是有作用范围的。这意味着我们可以在函数定义里导入特定的宏和函数:

1
2
3
4
5
6
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end

上述例子,被导入的 List.duplicate/2 只在特定的函数里才可见。duplicate/2 在这个模块(就这个问题而言,在任何其他模块)里的任何其他函数都不可用。

注意:import 一个模块自动就 require 它了

use

虽然不是指令,use却是与require紧密相关的一个宏,它允许你在当前上下文里使用一个模块。这个 use 宏常常被开发者用来把外部功能带入当前作用范围,经常是模块。

例如,为了用 ExUnit 框架写测试,开发者应该使用 ExUnit.Case 模块:

1
2
3
4
5
6
7
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end

背后的原理是,use require 给定的模块然后在它上面调用 __using/1__ 回调函数来允许这个模块注入一些代码到当前上下文。通常来说,如下的模块:

1
2
3
defmodule Example do
use Feature, option: :value
end

被编译成如下样子:

1
2
3
4
defmodule Example do
require Feature
Feature.__using__(option: :value)
end

理解alias

到此,你可能想知道:Elixir的别名到底是什么?它是如何表示的?

Elixir里的一个别名是一个大写开头的标识符(像String,Keyword,等等),在编译的时候它被转换成一个原子。例如,String 这个别名默认情况下被转换为 :”Elixir.String” :

1
2
3
4
5
6
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true

通过用 alias/2 指令,我们改变了别名所要转换成的原子。

别名转换为原子是因为在Erlang虚拟机里(也是Elixir里)模块总是用原子来表示。例如,如下就是我们用来调用Erlang模块的机制:

1
2
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]

模块嵌套

现在我们已经讨论了别名,我们可以讨论嵌套和它在Elixir里是如何运作的。考虑一下如下例子:

1
2
3
4
defmodule Foo do
defmodule Bar do
end
end

上面的例子将定义两个模块:Foo 和 Foo.Bar 。第二个模块可以在Foo里以Bar来访问只要它们在相同的作用范围里。上面的代码和下面的代码完全一样:

1
2
3
4
5
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end

如果,以后,Bar 模块被移到Foo模块定义的外面,它必须用完全限定名称(Foo.Bar)引用或者一个别名必须用前面讨论过的alias指令来设置。

注意:在Elixir里,你不必在定义Foo.Bar模块前必须要定义Foo模块,因为Elixir转换所有模块名字为原子。你可以定义任意嵌套的模块而不需要定义链上的任何模块(比如,定义Foo.Bar.Baz而没有先定义Foo或Foo.Bar)。

正如我们将在后面章节看到的,别名在宏里也扮演了一个至关重要的角色,来保证宏的整洁。

多重 alias/import/require/use

从Elixir v1.2开始,一次 alias、 import 或 require 多个模块是可能的。当我们开始嵌套模块这是特别有用的,当构建Elixir的应用程序时这是很常见的。例如,假设你有一个应用,所有模块都嵌套在MyApp下,你可以如下面例子一样一次性为MyApp.Foo,MyApp.Bar 和 MyApp.Baz指定别名:

1
alias MyApp.{Foo, Bar, Baz}

我们已经完成了我们的Elixir模块之旅。最后一个主题是模块属性。

原文链接: http://elixir-lang.org/getting-started/alias-require-and-import.html