别名,要求和导入 | alias, require, and import
别名、要求和导入
为了方便软件重用,Elixir 提供了三个指令(alias
,require
和import
)以及宏调用use
总结如下:
# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar
# Require the module in order to use its macros
require Foo
# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo
# Invokes the custom code defined in Foo as an extension point
use Foo
我们现在要详细探讨它们。请记住,前三个被称为指令,因为它们具有词汇范围
,而这use
是一个常见的扩展点。
别名
alias
允许您为任何给定的模块名称设置别名。
想象一下,一个模块使用一个专门的清单来实现Math.List
。该alias
指令允许参照Math.List
就像List
模块定义中:
defmodule Stats do
alias Math.List, as: List
# In the remaining module definition List expands to Math.List.
end
原始内容List
仍可以Stats
通过完全限定名称进行访问Elixir.List
。
注意:
Elixir
中定义的所有模块都是在主Elixir
名称空间内定义的。但是,为了方便起见,在引用它们时可以省略“Elixir
”。
别名经常用于定义快捷方式。实际上,alias
没有:as
选项的调用会自动将别名设置为模块名称的最后部分,例如:
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
在上面的例子中,因为我们alias
在函数内部调用plus/2
,所以别名仅在函数内有效plus/2
。minus/2
根本不会受到影响。
需要
Elixir提供宏作为元编程的机制(编写生成代码的代码)。宏在编译时被扩展。
模块中的公共功能是全局可用的,但为了使用宏,您需要通过要求在其中定义的模块来选择加入。
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
,我们需要首先需要Integer
模块。
请注意,像alias
指令一样,require
也是词法范围的。我们将在后面的章节中更多地讨论宏。
进口
我们使用import
任何时候我们想要从其他模块轻松访问函数或宏而不使用完全限定的名称。例如,如果我们想多次使用模块中的duplicate/2
函数List
,我们可以导入它:
iex> import List, only: [duplicate: 2]
List
iex> duplicate :ok, 3
[:ok, :ok, :ok]
在这种情况下,我们只duplicate
从中导入函数(带有参数2)List
。虽然: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]
duplicate(:ok, 10)
end
end
在上面的示例中,导入List.duplicate/2
仅在该特定功能中可见。duplicate/2
将不会在该模块(或任何其他模块)中的任何其他功能中使用。
请注意,import模块自动生成require。
使用
所谓use
宏是开发者经常用来给当前的词法作用域带来外部功能集成或者经常用到的模块的操作。
例如,为了使用ExUnit框架编写测试,开发人员应该使用该ExUnit.Case
模块:
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
在幕后,use
需要给定的模块,然后调用__using__/1
它的回调,允许模块向当前上下文中注入一些代码。一般来说,以下模块:
defmodule Example do
use Feature, option: :value
end
编译成
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
理解别名
在这一点上,你可能想知道:Elixir别名究竟是什么,它是如何表示的?
在Elixir的别名是一个大写识别码(例如String
,Keyword
等),这是在编译期间转换成原子。例如,String
别名默认转换为原子:"Elixir.String"
:
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true
通过使用alias/2
指令,我们正在改变别名扩展到的原子。
别名扩展到原子,因为在Erlang虚拟机(因此Elixir)模块总是由原子表示。例如,这就是我们用来调用Erlang模块的机制:
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]
模块嵌套
现在我们已经谈到了别名,我们可以谈论嵌套以及它如何在Elixir中工作。考虑下面的例子:
defmodule Foo do
defmodule Bar do
end
end
上面的例子将定义两个模块:Foo
和Foo.Bar
。第二个可以在Bar
里面进行访问,Foo
只要它们在相同的词汇范围内即可。上面的代码与以下代码完全相同:
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
如果稍后将Bar
模块移出Foo
模块定义,则必须使用其全名(Foo.Bar
)进行引用,或者必须使用alias
上面讨论的指令设置别名。
注意
:在Elixir中,在定义Foo
模块之前,您不必定义Foo.Bar
模块,因为语言会将所有模块名称转换为原子。您可以定义任意嵌套的模块,而无需在链中定义任何模块(例如,Foo.Bar.Baz
无需定义Foo
或Foo.Bar
第一个模块)。
正如我们在后面的章节中会看到的,别名在宏中也起着至关重要的作用,以确保它们的卫生。
多别名/导入/要求/使用
从Elixir v1.2开始,可以同时导入或需要多个模块。一旦我们开始嵌套模块,这非常有用,在构建Elixir应用程序时这很常见。例如,假设您有一个应用程序,其中所有模块都嵌套在下面MyApp
,您可以为模块别名MyApp.Foo
,MyApp.Bar
并且MyApp.Baz
一次如下:
alias MyApp.{Foo, Bar, Baz}
有了这个,我们完成了Elixir模块的巡视。最后要讲的话题是模块属性。