Spec-Zone.ru › Elixir 1.16

Исходный код Атрибуты модуля

Атрибуты модуля в Elixir служат трем целям:

  1. Они служат для аннотирования модуля, часто с информацией, используемой пользователем или виртуальной машиной.
  2. Они работают как константы.
  3. Они работают как временное хранилище модуля, используемое во время компиляции.

Давайте рассмотрим каждый случай по отдельности.

В качестве аннотаций

Elixir берет концепцию атрибутов модулей из Erlang. Например:

defmodule MyServer do
  @moduledoc "My server code."
end

В приведенном выше примере мы определяем документацию модуля, используя синтаксис атрибута модуля. В Elixir есть несколько зарезервированных атрибутов. Вот несколько из них, наиболее часто используемых:

  • @moduledoc — предоставляет документацию для текущего модуля.
  • @doc — предоставляет документацию для функции или макроса, следующего за атрибутом.
  • @spec — предоставляет тип спецификации для функции, следующей за атрибутом.
  • @behaviour — (обратите внимание на британское написание) используется для указания OTP-поведения или поведения, определённого пользователем.

@moduledoc и @doc являются, пожалуй, наиболее используемыми атрибутами, и мы ожидаем, что вы будете их часто использовать. Elixir рассматривает документацию как первоклассный элемент и предоставляет множество функций для доступа к документации. Мы рассмотрим их в отдельной главе.

Вернёмся к модулю Math, определённому в предыдущих главах, добавим к нему документацию и сохраним его в файле math.ex:

defmodule Math do
  @moduledoc """
  Provides math-related functions.

  ## Examples

      iex> Math.sum(1, 2)
      3

  """

  @doc """
  Calculates the sum of two numbers.
  """
  def sum(a, b), do: a + b
end

Elixir рекомендует использовать Markdown с heredocs для написания удобочитаемой документации. Heredocs — это многострочные строки, они начинаются и заканчиваются тройными двойными кавычками, сохраняя форматирование текста внутри. Мы можем получить доступ к документации любого скомпилированного модуля непосредственно из IEx:

$ elixirc math.ex
$ iex
iex> h Math # Access the docs for the module Math
...
iex> h Math.sum # Access the docs for the sum function
...

Мы также предоставляем инструмент под названием ExDoc, который используется для генерации HTML-страниц из документации.

Вы можете ознакомиться с документацией для Module, чтобы получить полный список поддерживаемых атрибутов. Elixir также использует атрибуты для определения типовых спецификаций, которые могут использоваться для объявления контрактов между модулями в дальнейшем.

В качестве "констант"

Разработчики Elixir часто используют атрибуты модулей, когда хотят сделать значение более видимым или многократно используемым:

defmodule MyServer do
  @initial_state %{host: "127.0.0.1", port: 3456}
  IO.inspect @initial_state
end

Попытка получить доступ к атрибуту, который не был определён, выведет предупреждение:

defmodule MyServer do
  @unknown
end
warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it before access

Атрибуты также можно считывать внутри функций:

defmodule MyServer do
  @my_data 14
  def first_data, do: @my_data
  @my_data 13
  def second_data, do: @my_data
end

MyServer.first_data #=> 14
MyServer.second_data #=> 13

Не добавляйте перенос строки между атрибутом и его значением, в противном случае Elixir будет считать, что вы читаете значение, а не устанавливаете его.

Функции могут вызываться при определении атрибута модуля:

defmodule MyApp.Status do
  @service URI.parse("https://example.com")
  def status(email) do
    SomeHttpClient.get(@service)
  end
end

Функция выше будет вызвана во время компиляции, и её возвращаемое значение, а не сам вызов функции, будет подставлено в атрибут. Таким образом, вышесказанное фактически скомпилируется в следующее:

defmodule MyApp.Status do
  def status(email) do
    SomeHttpClient.get(%URI{
      authority: "example.com",
      host: "example.com",
      port: 443,
      scheme: "https"
    })
  end
end

Это может быть полезно для предварительного вычисления значений констант, но это также может вызвать проблемы, если вы ожидаете, что функция будет вызвана во время выполнения. Например, если вы читаете значение из базы данных или переменной окружения внутри атрибута, помните, что оно будет прочитано только во время компиляции. Однако обратите внимание, что вы не можете вызывать функции, определённые в том же модуле, как часть самого атрибута, поскольку эти функции ещё не определены.

Каждый раз, когда атрибут считывается внутри функции, Elixir делает моментальную фотографию его текущего значения. Поэтому, если вы читаете один и тот же атрибут несколько раз внутри нескольких функций, вы можете получить несколько копий его значения. Обычно это не проблема, но если вы используете функции для вычисления больших атрибутов модуля, это может замедлить компиляцию. Решением является перемещение атрибута в общую функцию. Например, вместо этого:

def some_function, do: do_something_with(@example)
def another_function, do: do_something_else_with(@example)

Предпочтительнее это:

def some_function, do: do_something_with(example())
def another_function, do: do_something_else_with(example())
defp example, do: @example

Если @example легко вычислить, может быть даже лучше обойтись без атрибута модуля и вычислить его значение внутри функции.

Накопление атрибутов

Обычно повторное определение атрибута модуля приводит к его переприсвоению, но существуют ситуации, когда вы можете настроить атрибут модуля таким образом, чтобы его значения накапливались:

defmodule Foo do
  Module.register_attribute(__MODULE__, :param, accumulate: true)

  @param :foo
  @param :bar
  # here @param == [:bar, :foo]
end

В качестве временного хранилища

Чтобы увидеть пример использования атрибутов модулей в качестве хранилища, обратите внимание на фреймворк для написания юнит-тестов Elixir под названием ExUnit. ExUnit использует атрибуты модулей для различных целей:

defmodule MyTest do
  use ExUnit.Case, async: true

  @tag :external
  @tag os: :unix
  test "contacts external service" do
    # ...
  end
end

В приведенном выше примере ExUnit сохраняет значение async: true в атрибуте модуля, чтобы изменить способ компиляции модуля. Теги также определены как атрибуты accumulate: true, и они хранят теги, которые могут использоваться для настройки и фильтрации тестов. Например, вы можете избежать запуска внешних тестов на вашем компьютере, поскольку они медленные и зависят от других сервисов, хотя их можно всё ещё включить в вашей системе сборки.

Для понимания лежащего в основе кода нам нужны макросы, поэтому мы вернёмся к этому шаблону в руководстве по метапрограммированию и узнаем, как использовать атрибуты модулей в качестве хранилища, чтобы позволить разработчикам создавать языки предметной области (DSL).

В следующих главах мы изучим структуры и протоколы, прежде чем перейдём к обработке исключений и другим конструкциям, таким как сигилы и генераторы.

← Предыдущая страница alias, require и import
Следующая страница → Структуры

Скачать версию ePub

Создано с помощью ExDoc (v0.32.2) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/module-attributes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API