Spec-Zone.ru › Elixir 1.18

Source Атрибуты модуля

Атрибуты модуля в 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 также использует атрибуты для аннотирования нашего кода типами.

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

До сих пор мы видели, как определять атрибуты, но как их можно прочитать? Давайте посмотрим на пример:

defmodule MyServer do
  @service URI.parse("https://example.com")
  IO.inspect(@service)
end

Новые строки

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

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

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

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

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 создаёт снимок его текущего значения. Поэтому, если вы читаете один и тот же атрибут несколько раз внутри нескольких функций, вы увеличиваете время компиляции, так как 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

В качестве констант, вычисляемых на этапе компиляции

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

@hours_in_a_day 24

Вы должны предпочесть:

defp hours_in_a_day(), do: 24

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

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

defp system_config(), do: %{timezone: "Etc/UTC", locale: "pt-BR"}

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

Случаи использования атрибутов модуля возникают, когда вам нужно выполнить некоторую работу на этапе компиляции и затем встроить её результаты внутрь функции. Распространённым случаем является использование атрибутов модулей в шаблонах и условиях (в качестве альтернативы defguard/1), так как они поддерживают ограниченный набор выражений:

# Inside pattern
@default_timezone "Etc/UTC"
def shift(@default_timezone), do: ...

# Inside guards
@time_periods [:am, :pm]
def shift(time, period) when period in @time_periods, do: ...

Атрибуты модуля как константы и как временное хранилище чаще всего используются вместе: атрибут модуля используется для вычисления и хранения дорогостоящего значения, а затем экспонируется как константа из этого модуля.

Дальнейшее изучение

Библиотеки и фреймворки могут использовать атрибуты модулей для предоставления пользовательских аннотаций. Для примера обратитесь к фреймворку для тестирования юнит-тестов 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 в атрибуте модуля, чтобы изменить способ компиляции модуля. Теги также работают как аннотации, и их можно предоставлять несколько раз благодаря способности Elixir накапливать атрибуты. Затем вы можете использовать теги для настройки и фильтрации тестов, например, чтобы избежать выполнения тестов, специфичных для Unix, при запуске вашей тестовой среды на Windows.

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

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

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

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

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

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

Spec-Zone.ru

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