alias, require和import为了达成软件的复用性,Elixir提供了三个命令。我将在下面几章看到,它们之所以被称为指令是因为它们有作用域。
aliasalias允许你给任何模块设置别名。想象一下我们的Math模块使用了一个特别的列表实现用于一些特殊的数学计算:
defmodule Math do
alias Math.List, as: List
end
那么从现在开始,任何对List的引用都会被扩展成Math.List。如果你想要使用原版的List模块,它还是可以在命名控件Elixir里找到:
List.flatten #=> uses Math.List.flatten
Elixir.List.flatten #=> uses List.flatten
Elixir.Math.List.flatten #=> uses Math.List.flatten
注意:Elixir中的所有模块都定义在一个主命名空间
Elixir下。然而,在引用的时候,你可以忽略它。
别名经常被用于定义快捷方式。实际上,不用选项as调用alias会自动指向模块名的最后一个部分,比如:
alias Math.List
等同于:
alias Math.List, as: List
注意alias是有作用域的,别名只能在某个函数内部起作用:
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
在上面的例子中, 因为我们在函数plus/2内部启用了alias,这个别名只在函数plus/2内部有效。plus/2则不会被影响。
requireElixir提供了宏作为元编程(用代码产生代码)的机制。
宏是在编译时被执行和扩展的代码。这意味着,为了能使用宏,我们需要保证这些模块和实现是在编译时可用的。这可以用require来达成:
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是一个可以被当守护使用的宏。这意味着,为了调用Integer.odd?/1,我们需要首先引入Integer模块。
总的来说,一个模块在被使用之前无需被引入,除非我们需要那么模块中的宏。调用一个不可得的宏会导致一个错误。注意和alias一样,require也是有作用域的。我们将在更详细地讨论宏。
import用import可以非常方面地直接引入其他模块中的函数和宏。例如,如果我们需要反复用到List模块中的duplicate函数,我们能简单地引入它:
iex> import List, only: [duplicate: 2]
nil
iex> duplicate :ok, 3
[:ok, :ok, :ok]
在这个例子中,我们仅仅从List里引入了函数duplicate(参数量2)。虽然only:并非是必须的,还是推荐使用。另一个可选项是except。
import也支持:macros和:functions和:only一起使用。例如,要引入所有的宏,可以这么写:
import Integer, only: :macros
或这引入全部的函数:
import Integer, only: :functions
注意import也是有作用域的,这意味着我们能在特定函数内引入特定的宏:
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
# call duplicate
end
end
在上面的例子中,引入的List.duplicate/2只在那个函数内部才可见。duplicate/2对这个模块内的其他函数都是不可用的(更不用说其他的模块了)
注意引入一个模块会自动requires它
到这李,也许你在好奇Elixir中的别名到底是什么?它们究竟是如何存在的?
Elixir中的别名首先是一个首字母大写的识别符(像String,Keyword之类),会在编译时转换成原子。例如,别名String默认翻译成:"Elixir.String":
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String"
String
使用了alias/2,我们就能改变了一个别名最后翻译的结果。
之所以这样是因为在Erlang虚拟机(随之在Elixir)中,模块是用原子表现的。例如,下面是我们如何效用Erlang的模块的:
iex> :lists.flatten([1,[2],3])
[1, 2, 3]
这套机制同时也允许我们动态地代用模块内的一个函数:
iex> mod = :lists
:lists
iex> mod.flatten([1,[2],3])
[1,2,3]
也就是说,我们直接在原子:lists上调用函数flatten。
讨论完了别名,我们可以接着谈谈嵌套,以及它是如何工作的。考虑一下下面的代码:
defmodule Foo do
defmodule Bar do
end
end
上面的这个例子定义了两个模块Foo和Foo.Bar。只要它们一直在同一个作用域内,第二个模块都可以作为Foo内部的Bar被访问到。如果之后开发者决定把Bar移到另一个文件,引用它就需要全名(Foo.Bar)了,要么像我们之前讨论的一样,用alias设置一个别名。
也就是说,上面的代码等价于:
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
随后几章我们将看到,别名也在宏里扮演了关键的角色,用来保证它们的同构性。到这里我们差不多完成了Elixir中的模块部分,下一章是关于模块的属性的。