当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Elixir Module用法及代码示例


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

您可以将 falsetrue 或特定行为传递给 @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

自定义属性

除了上面列出的内置属性外,还可以添加自定义属性。自定义属性使用 @/1 运算符表示,后跟有效的变量名。赋予自定义属性的值必须是有效的 Elixir 值:

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