檢視原始碼 alias、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 提供巨集作為元程式設計(編寫產生程式碼的程式碼)的機制。巨集會在編譯時擴充。
模組中的公開函式可供全球使用,但若要使用巨集,您需要透過 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
,還有 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`
正如我們在後面的章節中所看到的,別名在巨集中也扮演著至關重要的角色,以確保它們是衛生的。
多個別名/匯入/需要/使用
可以一次 alias
、import
、require
或 use
多個模組。這在我們開始巢狀模組時特別有用,這在建構 Elixir 應用程式時非常常見。例如,想像你有一個應用程式,其中所有模組都巢狀在 MyApp
下,你可以一次別名模組 MyApp.Foo
、MyApp.Bar
和 MyApp.Baz
,如下所示
alias MyApp.{Foo, Bar, Baz}
有了這個,我們就完成了 Elixir 模組的導覽。接下來要介紹的主題是模組屬性。