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。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。