Spec-Zone.ru › Elixir 1.17

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

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

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

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

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

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

Spec-Zone.ru

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