查看源代码 别名、require 和 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
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
仍然可以通过完全限定名称 Elixir.List
在 Stats
中访问。
在 Elixir 中定义的所有模块都在主
Elixir
命名空间中定义,例如Elixir.String
。但是,为了方便起见,您可以省略“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
中有效。 minus/2
根本不会受到影响。
require
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
(elixir) Integer.is_odd(3)
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
在 Elixir 中,Integer.is_odd/1
被定义为宏,以便可以用作保护。这意味着,为了调用 Integer.is_odd/1
,我们需要先需要 Integer
模块。
请注意,与 alias
指令一样,require
也是词法作用域的。我们将在后面的章节中更多地讨论宏。
import
每当我们想要在不使用完全限定名称的情况下访问来自其他模块的函数或宏时,我们都会使用 import
。请注意,我们只能导入公共函数,因为私有函数永远无法从外部访问。
例如,如果我们想要多次使用 List
模块中的 duplicate/2
函数,我们可以将其导入
iex> import List, only: [duplicate: 2]
List
iex> duplicate(:ok, 3)
[:ok, :ok, :ok]
我们只从 List
导入了 duplicate
函数(带 2 元)。虽然 :only
是可选的,但建议使用它,以避免将给定模块的所有函数导入到当前范围内。也可以使用 :except
作为选项,以便导入模块中的所有内容,除了函数列表之外。
请注意,import
也是词法作用域的。这意味着我们可以在函数定义中导入特定宏或函数
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
在上面的示例中,导入的 List.duplicate/2
仅在该特定函数中可见。 duplicate/2
在该模块中的任何其他函数(或任何其他模块)中都不可用。
虽然 import
可能对框架和库构建抽象很有用,但开发人员通常应该在自己的代码库中更喜欢 alias
而不是 import
,因为别名使被调用的函数的来源更清晰。
use
use
宏经常用作扩展点。这意味着,当您 use
模块 FooBar
时,您允许该模块在当前模块中注入任何代码,例如导入自身或其他模块、定义新函数、设置模块状态等。
例如,为了使用 ExUnit 框架编写测试,开发人员应该使用 ExUnit.Case
模块
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
在幕后,use
需要给定的模块,然后在它上面调用 __using__/1
回调,允许模块将一些代码注入到当前上下文中。一些模块(例如,上面的 ExUnit.Case
,还有 Supervisor
和 GenServer
)使用这种机制来为您的模块填充一些基本行为,您的模块应该覆盖或完成这些行为。
一般来说,以下模块
defmodule Example do
use Feature, option: :value
end
编译为
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
由于 use
允许任何代码运行,因此我们无法真正知道使用模块的副作用,除非阅读其文档。因此,请谨慎使用此函数,仅在严格需要时才使用。不要在 import
或 alias
可以做到的地方使用 use
。
理解别名
此时,您可能想知道: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)中,模块始终由原子表示
iex> List.flatten([1, [2], 3])
[1, 2, 3]
iex> :"Elixir.List".flatten([1, [2], 3])
[1, 2, 3]
这就是我们用于调用 Erlang 模块的机制
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]
模块嵌套
既然我们已经讨论了别名,现在我们可以谈论嵌套以及它在 Elixir 中是如何工作的。考虑以下示例
defmodule Foo do
defmodule Bar do
end
end
上面的示例将定义两个模块:Foo
和 Foo.Bar
。只要它们在相同的词法作用域中,第二个模块可以在 Foo
中访问为 Bar
。
如果稍后将 Bar
模块移出 Foo
模块定义,则必须通过其完整名称 (Foo.Bar
) 引用它,或者必须使用上面讨论的 alias
指令设置别名。
注意:在 Elixir 中,您不必在能够定义 Foo.Bar
模块之前先定义 Foo
模块,因为它们实际上是独立的。上面的代码也可以写成
defmodule Foo.Bar do
end
defmodule Foo do
alias Foo.Bar
# Can still access it as `Bar`
end
对嵌套模块进行别名化不会将父模块引入范围。考虑以下示例
defmodule Foo do
defmodule Bar do
defmodule Baz do
end
end
end
alias Foo.Bar.Baz
# The module `Foo.Bar.Baz` is now available as `Baz`
# However, the module `Foo.Bar` is *not* available as `Bar`
正如我们将在后面的章节中看到的,别名在宏中也起着至关重要的作用,以确保它们是卫生的。
多别名/import/require/use
可以同时 alias
、import
、require
或 use
多个模块。这在我们开始嵌套模块时特别有用,这在构建 Elixir 应用程序时非常常见。例如,假设您有一个应用程序,其中所有模块都嵌套在 MyApp
下,您可以像这样同时对 MyApp.Foo
、MyApp.Bar
和 MyApp.Baz
模块进行别名化
alias MyApp.{Foo, Bar, Baz}
这样,我们就完成了对 Elixir 模块的介绍。下一个要讨论的主题是模块属性。