别名,要求和导入

为了方便软件重用,Elixir 提供了三个指令(aliasrequireimport)以及宏调用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/2minus/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的别名是一个大写识别码(例如StringKeyword等),这是在编译期间转换成原子。例如,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

上面的例子将定义两个模块:FooFoo.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无需定义FooFoo.Bar第一个模块)。

正如我们在后面的章节中会看到的,别名在宏中也起着至关重要的作用,以确保它们的卫生。

多别名/导入/要求/使用

从Elixir v1.2开始,可以同时导入或需要多个模块。一旦我们开始嵌套模块,这非常有用,在构建Elixir应用程序时这很常见。例如,假设您有一个应用程序,其中所有模块都嵌套在下面MyApp,您可以为模块别名MyApp.FooMyApp.Bar并且MyApp.Baz一次如下:

alias MyApp.{Foo, Bar, Baz}

有了这个,我们完成了Elixir模块的巡视。最后要讲的话题是模块属性。