Атрибуты модулей в Эликсире решают три задачи:
- Они служат для добавления модулю аннотаций с информацией, которая может использоваться пользователем или виртуальной машиной.
- Они работают как константы.
- Они работают как временное хранилище модуля, для использования во время компиляции.
Поговорим о каждом случае отдельно.
Аннотации
Эликсир унаследовал концепцию атрибутов модулей из Эрланга. Взгляните на пример:
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
используются для аннотирования тестов. Теги можно дальше использовать для фильтрации тестов. Например, вы можете избежать запуска внешних тестов на вашей машине, потому что они медленные и зависят от других сервисов, и они могут быть запущены на системе для сборки.
Мы надеемся, эта секция проливает немного света на то, как Эликсир поддерживает метапрограммирование, и что атрибуты модулей играют важную роль для этого.
В следующих главах мы рассмотрим структуры и протоколы, перед тем как перейти к обработке исключений и другим конструкциям, таким как сигилы и списковые выражения.