檢視原始碼 alias、require 和 import

為了促進軟體再利用,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

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.ListStats 中存取。

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 提供巨集作為元程式設計(編寫產生程式碼的程式碼)的機制。巨集會在編譯時擴充。

模組中的公開函式可供全球使用,但若要使用巨集,您需要透過 require 他們所定義的模組來選擇加入。

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,我們需要先 require 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,還有 SupervisorGenServer)使用此機制在您的模組中填充一些基本行為,您的模組旨在覆寫或完成這些行為。

一般來說,下列模組

defmodule Example do
  use Feature, option: :value
end

編譯成

defmodule Example do
  require Feature
  Feature.__using__(option: :value)
end

由於 use 允許任何程式碼執行,我們無法真正知道使用模組的副作用,除非閱讀其文件。因此,請謹慎使用此函式,並且僅在絕對必要時才使用。不要在 importalias 可以做到的地方使用 use

了解別名

在這個時候,您可能會想: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)中,模組總是使用原子表示

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

上述範例將定義兩個模組:FooFoo.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`

正如我們在後面的章節中所看到的,別名在巨集中也扮演著至關重要的角色,以確保它們是衛生的。

多個別名/匯入/需要/使用

可以一次 aliasimportrequireuse 多個模組。這在我們開始巢狀模組時特別有用,這在建構 Elixir 應用程式時非常常見。例如,想像你有一個應用程式,其中所有模組都巢狀在 MyApp 下,你可以一次別名模組 MyApp.FooMyApp.BarMyApp.Baz,如下所示

alias MyApp.{Foo, Bar, Baz}

有了這個,我們就完成了 Elixir 模組的導覽。接下來要介紹的主題是模組屬性。