Elixir語言中 Module
相關用法介紹如下。
提供在編譯期間處理模塊的函數。
它允許開發人員動態添加、刪除和注冊屬性、附加文檔等。
編譯模塊後,使用該模塊中的許多函數會引發錯誤,因為檢查運行時數據超出了它們的範圍。大多數運行時數據可以通過附加到每個編譯模塊的
函數來檢查。__info__/1
模塊屬性
每個模塊都可以用一個或多個屬性來裝飾。 Elixir 目前定義了以下幾個:
@after_compile
將在當前模塊編譯後立即調用的鉤子。接受模塊或 {module, function_name}
。請參閱下麵的"Compile callbacks" 部分。
@before_compile
在編譯模塊之前將調用的鉤子。接受一個模塊或一個 {module, function_or_macro_name}
元組。請參閱下麵的"Compile callbacks" 部分。
@behaviour
注意英式拚寫!
模塊可以引用行為,以確保它們實現 @callback
定義的所需特定函數簽名。
例如,您可以指定 URI.Parser
行為,如下所示:
defmodule URI.Parser do
@doc "Defines a default port"
@callback default_port() :: integer
@doc "Parses the given URL"
@callback parse(uri_info :: URI.t()) :: URI.t()
end
然後一個模塊可以將其用作:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
如果行為發生變化或 URI.HTTP
未實現其中一個回調,則會引發警告。
有關詳細文檔,請參閱 behaviour typespec documentation 。
@impl
為了幫助正確實現行為,您可以選擇聲明@impl
以實現行為的回調。這使得回調顯式並且可以幫助您捕獲代碼中的錯誤。編譯器會在這些情況下發出警告:
-
如果您在該函數不是回調時使用
@impl
標記函數。 -
如果你沒有用
@impl
標記一個函數,而其他函數用@impl
標記。如果您使用@impl
標記一個函數,則必須將該行為的所有其他回調標記為@impl
。
@impl
在 per-context 的基礎上工作。如果您通過宏生成函數並用 @impl
標記它,則不會影響生成該函數的模塊。
@impl
還通過向其他開發人員明確該函數正在實現回調來幫助提高可維護性。
使用 @impl
,上麵的例子可以重寫為:
defmodule URI.HTTP do
@behaviour URI.Parser
@impl true
def default_port(), do: 80
@impl true
def parse(info), do: info
end
您可以將 false
、 true
或特定行為傳遞給 @impl
。
defmodule Foo do
@behaviour Bar
@behaviour Baz
# Will warn if neither Bar nor Baz specify a callback named bar/0.
@impl true
def bar(), do: :ok
# Will warn if Baz does not specify a callback named baz/0.
@impl Baz
def baz(), do: :ok
end
代碼現在更具可讀性,因為現在可以清楚哪些函數是 API 的一部分,哪些是回調實現。為了強化這個想法,@impl true
自動將函數標記為 @doc false
,除非明確設置 @doc
否則禁用文檔。
@compile
定義模塊編譯的選項。這用於配置 Elixir 和 Erlang 編譯器,就像外部工具添加的任何其他編譯過程一樣。例如:
defmodule MyModule do
@compile {:inline, my_fun: 1}
def my_fun(arg) do
to_string(arg)
end
end
@compile
的多次使用將累積而不是覆蓋以前的使用。請參閱下麵的"Compile options" 部分。
@deprecated
提供函數的棄用原因。例如:
defmodule Keyword do
@deprecated "Use Kernel.length/1 instead"
def size(keyword) do
length(keyword)
end
end
Mix 編譯器會自動查找對已棄用模塊的調用,並在編譯期間發出警告。
使用@deprecated
屬性也將反映在給定函數和宏的文檔中。您可以在 @deprecated
屬性和文檔元數據之間進行選擇,以提供 hard-deprecations(帶警告)和 soft-deprecations(不帶警告):
這是一個soft-deprecation,因為它隻是將文檔注釋為已棄用:
@doc deprecated: "Use Kernel.length/1 instead"
def size(keyword)
這是一個hard-deprecation,因為它會發出警告並將文檔注釋為已棄用:
@deprecated "Use Kernel.length/1 instead"
def size(keyword)
目前@deprecated
僅支持函數和宏。但是,您也可以使用注釋元數據中的:deprecated
鍵來注釋模塊、類型和回調的文檔。
我們建議謹慎使用此函數,尤其是庫作者。棄用代碼總是將負擔推給 Library 用戶。我們還建議長時間維護已棄用的函數,即使在棄用之後也是如此,以便開發人員有足夠的時間進行更新(除非不希望保留已棄用的 API,例如存在安全問題)。
@doc
和 @typedoc
為屬性後麵的實體提供文檔。 @doc
與函數、宏、回調或宏回調一起使用,而@typedoc
與類型(公共或不透明)一起使用。
接受字符串(通常是heredoc)或false
,其中@doc false
將使實體對
等文檔提取工具不可見。例如:ExDoc
defmodule MyModule do
@typedoc "This type"
@typedoc since: "1.1.0"
@type t :: term
@doc "Hello world"
@doc since: "1.1.0"
def hello do
"world"
end
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
end
end
從上麵的示例中可以看出,@doc
和 @typedoc
還接受一個關鍵字列表,該列表用作提供有關實體的任意元數據的一種方式。
和ExDoc
等工具可以使用此信息來顯示注釋。一個常見的用例是IEx
since
,它可用於注釋該函數是在哪個版本中引入的。
如示例中所示,可以在實體之前多次使用這些屬性。但是,如果與二進製文件一起使用兩次,編譯器會發出警告,因為它會替換前麵使用的文檔文本。多次使用關鍵字列表會將列表合並為一個。
請注意,由於編譯器還定義了一些額外的元數據,因此有一些保留鍵將被忽略並在使用時發出警告。目前這些是: :opaque
和 :defaults
。
編譯此模塊後,此信息可通過
函數獲得。Code.fetch_docs/1
@dialyzer
定義在使用支持模塊屬性的:dialyzer
版本時請求或抑製的警告。
接受一個原子、一個元組或一個原子和元組的列表。例如:
defmodule MyModule do
@dialyzer {:nowarn_function, my_fun: 1}
def my_fun(arg) do
M.not_a_function(arg)
end
end
有關支持的警告列表,請參閱 :dialyzer 模塊。
@dialyzer
的多次使用將累積而不是覆蓋以前的使用。
@external_resource
指定當前模塊的外部資源。
有時,模塊會嵌入來自外部文件的信息。該屬性允許模塊注釋哪些外部資源已被使用。
工具可以使用此信息來確保在任何外部資源發生更改時重新編譯模塊,例如:
。mix compile.elixir
如果外部資源不存在,模塊仍然對其有依賴,導致模塊一添加文件就重新編譯。
@file
更改堆棧跟蹤中使用的文件名,用於跟隨屬性的函數或宏,例如:
defmodule MyModule do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
@moduledoc
提供當前模塊的文檔。
defmodule MyModule do
@moduledoc """
A very useful module.
"""
@moduledoc authors: ["Alice", "Bob"]
end
接受字符串(通常是heredoc)或false
,其中@moduledoc false
將使模塊對
等文檔提取工具不可見。ExDoc
與@doc
類似,也接受關鍵字列表以提供有關模塊的元數據。有關詳細信息,請參閱上麵@doc
的文檔。
編譯此模塊後,此信息可通過
函數獲得。Code.fetch_docs/1
@on_definition
定義當前模塊中的每個函數或宏時將調用的鉤子。在注釋函數時很有用。
接受一個模塊或一個 {module, function_name}
元組。請參閱下麵的"Compile callbacks" 部分。
@on_load
每當加載模塊時都會調用的鉤子。
接受當前模塊中函數的函數名稱(作為原子)或{function_name, 0}
元組,其中function_name
是當前模塊中的函數名稱。該函數的元數必須為 0(無參數)。如果函數沒有返回 :ok
,模塊的加載將被中止。例如:
defmodule MyModule do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
:abort
end
end
def some_condition do
false
end
end
用HiPE 編譯的模塊不會調用這個鉤子。
@vsn
指定模塊版本。接受任何有效的 Elixir 值,例如:
defmodule MyModule do
@vsn "1.0"
end
結構屬性
-
@derive
- 為當前模塊中定義的結構派生給定協議的實現 -
@enforce_keys
- 確保在構建當前模塊中定義的結構時始終設置給定的鍵
有關構建和使用結構的更多信息,請參閱
。Kernel.defstruct/1
類型規範屬性
以下屬性是 typespecs 的一部分,也是 Elixir 內置的:
@type
- 定義要在@spec
中使用的類型@typep
- 定義要在@spec
中使用的私有類型@opaque
- 定義要在@spec
中使用的不透明類型@spec
- 提供函數規範@callback
- 提供行為回調規範@macrocallback
- 為宏行為回調提供規範@optional_callbacks
- 指定哪些行為回調和宏行為回調是可選的@impl
- 聲明回調函數或宏的實現
有關詳細文檔,請參閱 typespec documentation 。
自定義屬性
除了上麵列出的內置屬性外,還可以添加自定義屬性。自定義屬性使用
運算符表示,後跟有效的變量名。賦予自定義屬性的值必須是有效的 Elixir 值:@/1
defmodule MyModule do
@custom_attr [some: "stuff"]
end
有關定義自定義屬性時可用的更多高級選項,請參閱
。register_attribute/3
編譯回調
在定義函數時以及在生成模塊字節碼之前和之後立即調用三個回調。
@after_compile
將在當前模塊編譯後立即調用的鉤子。
接受一個模塊或一個 {module, function_name}
元組。該函數必須接受兩個參數:模塊環境及其字節碼。當隻提供一個模塊時,該函數被假定為 __after_compile__/2
。
回調將按照它們注冊的順序運行。
示例
defmodule MyModule do
@after_compile __MODULE__
def __after_compile__(env, _bytecode) do
IO.inspect(env)
end
end
@before_compile
在編譯模塊之前將調用的鉤子。
接受一個模塊或一個 {module, function_or_macro_name}
元組。函數/宏必須有一個參數:模塊環境。如果它是一個宏,它的返回值將在編譯開始之前在模塊定義的末尾注入。
當隻提供一個模塊時,函數/宏被假定為 __before_compile__/1
。
回調將按照它們注冊的順序運行。任何可覆蓋的定義都將在第一個回調運行之前具體化。在編譯回調之前,可以在另一個定義中再次覆蓋定義,並且在所有回調運行後最後一次將其具體化。
Note
:與 @after_compile
不同,回調函數/宏必須放在單獨的模塊中(因為調用回調時,當前模塊還不存在)。
示例
defmodule A do
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
end
end
end
defmodule B do
@before_compile A
end
B.hello()
#=> "world"
@on_definition
定義當前模塊中的每個函數或宏時將調用的鉤子。在注釋函數時很有用。
接受一個模塊或一個 {module, function_name}
元組。該函數必須接受 6 個參數:
- 模塊環境
- 函數/宏的類型:
:def
、:defp
、:defmacro
或:defmacrop
- 函數/宏名稱
- 引用的參數列表
- 引用的守衛名單
- 引用的函數體
如果定義的函數/宏有多個子句,則將為每個子句調用鉤子。
與其他鉤子不同,@on_definition
隻會調用函數而不會調用宏。這是為了避免@on_definition
回調重新定義剛剛定義的函數以支持更明確的方法。
當隻提供一個模塊時,該函數被假定為 __on_definition__/6
。
示例
defmodule Hooks do
def on_def(_env, kind, name, args, guards, body) do
IO.puts("Defining #{kind} named #{name} with args:")
IO.inspect(args)
IO.puts("and guards")
IO.inspect(guards)
IO.puts("and body")
IO.puts(Macro.to_string(body))
end
end
defmodule MyModule do
@on_definition {Hooks, :on_def}
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
end
def hello(_) do
:ok
end
end
編譯選項
@compile
屬性接受 Elixir 和 Erlang 編譯器使用的不同選項。下麵記錄了一些常見的用例:
-
@compile :debug_info
- 包括:debug_info
,與Code.get_compiler_option/1
-
@compile {:debug_info, false}
- 無論Code.get_compiler_option/1
:debug_info
-
@compile {:inline, some_fun: 2, other_fun: 3}
- 內聯給定的名稱/數量對。內聯在本地應用,來自另一個模塊的調用不受此選項的影響 -
@compile {:autoload, false}
- 編譯後禁用自動加載模塊。相反,模塊將在它被分派到之後被加載 -
@compile {:no_warn_undefined, Mod}
或@compile {:no_warn_undefined, {Mod, fun, arity}}
- 如果未定義給定模塊或給定Mod.fun/arity
,則不會發出警告
相關用法
- Elixir Module.defines?用法及代碼示例
- Elixir Module.concat用法及代碼示例
- Elixir Module.delete_attribute用法及代碼示例
- Elixir Module.eval_quoted用法及代碼示例
- Elixir Module.put_attribute用法及代碼示例
- Elixir Module.split用法及代碼示例
- Elixir Module.overridables_in用法及代碼示例
- Elixir Module.definitions_in用法及代碼示例
- Elixir Module.has_attribute?用法及代碼示例
- Elixir Module.get_attribute用法及代碼示例
- Elixir Module.reserved_attributes用法及代碼示例
- Elixir Module.register_attribute用法及代碼示例
- Elixir Module.create用法及代碼示例
- Elixir Module.safe_concat用法及代碼示例
- Elixir Module.attributes_in用法及代碼示例
- Elixir Map.keys用法及代碼示例
- Elixir Map.values用法及代碼示例
- Elixir Map.update用法及代碼示例
- Elixir MapSet用法及代碼示例
- Elixir Map.split用法及代碼示例
- Elixir MapSet.intersection用法及代碼示例
- Elixir Map.put_new_lazy用法及代碼示例
- Elixir Map.drop用法及代碼示例
- Elixir Macro.Env.fetch_alias用法及代碼示例
- Elixir Map用法及代碼示例
注:本文由純淨天空篩選整理自elixir-lang.org大神的英文原創作品 Module。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。