Атрибуты модулей

Атрибуты модулей в Эликсире решают три задачи:

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

Поговорим о каждом случае отдельно.

Аннотации

Эликсир унаследовал концепцию атрибутов модулей из Эрланга. Взгляните на пример:

defmodule MyServer do
  @vsn 2
end

Здесь добавляется атрибут @vsn с версией модуля. Он используется в механизме перезагрузки кода, в виртуальной машине Эрланга для проверки, был ли модуль обновлён. Если версия не указана, в её качестве устанавливается контрольная сумма MD5-функций модуля.

В Эликсире есть несколько зарезервированных атрибутов. Вот наиболее часто используемые:

  • @moduledoc – для предоставления документации к текущему модулю.
  • @doc – для документации к функции или макросу, следующими за атрибутом.
  • @behaviour – для определения OTP или определённого пользователем поведения. Обратите внимание на британское написание.
  • @before_compile – для хука, который будет выполнен до компиляции модуля. Благодаря этому возможно внедрять функции внутрь модуля прямо перед компиляцией.

Атрибуты модуля @moduledoc и @doc используются чаще всего, и мы ожидаем, что вы тоже будете часто использовать их. Документация очень важна в Эликсире, есть множество функций для доступа к ней. Вы можете больше прочитать о написании документации в Эликсире в официальной документации.

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

defmodule Math do
  @moduledoc """
  Предоставляет математические функции.

  ## Примеры

      iex> Math.sum(1, 2)
      3

  """

  @doc """
  Вычисляет сумму двух чисел.
  """
  def sum(a, b), do: a + b
end

Эликсир поощряет использование разметки Markdown и синтаксиса Heredoc для написания читаемой документации. Heredoc – многострочные строки, они начинаются и заканчиваются утроенными двойными кавычками, сохраняя форматирование внутреннего текста. Мы можем получить доступ к документации любого скомпилированного модуля прямо из IEx:

$ elixirc math.ex
$ iex
iex> h Math # Доступ к документам модуля `Math`
...

iex> h Math.sum # Доступ к документам функции `sum`
...

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

Вы можете обратиться к документации по модулям, чтобы получить полный список поддерживаемых атрибутов. Эликсир также использует атрибуты для определения «Спецификации типов и поведения».

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

Константы

Эликсир-разработчики часто используют атрибуты модулей как константы:

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

Обратите внимание. В отличии от Эрланга, пользовательские атрибуты не хранятся в модуле по умолчанию. Значение существует только во время компиляции. Разработчик может настроить поведение атрибутов, близкое к Эрлангу, вызвав Module.register_attribute/3.

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

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

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

Временное хранилище

Один из проектов организации Эликсир – проект Plug, который должен стать одной из основ создания веб-библиотек и фреймворков на Эликсире.

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

defmodule MyPlug do
  use Plug.Builder

  plug :set_header
  plug :send_ok

  def set_header(conn, _opts) do
    put_resp_header(conn, "x-header", "set")
  end

  def send_ok(conn, _opts) do
    send(conn, 200, "ok")
  end
end

IO.puts "Running MyPlug with Cowboy on http://localhost:4000"
Plug.Adapters.Cowboy.http MyPlug, []

В примере выше, мы использовали макрос plug/1 для подключения функций, которые могут быть вызваны, когда поступает веб-запрос. Внутри, каждый раз, когда вы вызывает plug/1, библиотека Plug хранит переданный аргумент как атрибут @plug. Прямо перед компиляцией модуля, Plug запускает функцию обратного вызова, котораяописывает функцию call/2, которая обрабатывает HTTP-запрос. Эта функция запустит все, что есть в @plugs по очереди.

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

Другой пример можно взять во фреймворке ExUnit, который использует атрибуты модулей как аннотации и хранилище:

defmodule MyTest do
  use ExUnit.Case

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

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

Мы надеемся, эта секция проливает немного света на то, как Эликсир поддерживает метапрограммирование, и что атрибуты модулей играют важную роль для этого.

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

© 2020 / Россия Любые мысли и вопросы пишите на elixir@wunsh.ru.