Для повторного использования частей приложения в Эликсире есть три директивы: alias, require и import, а также макрос use – описанные ниже:
# Псевдоним для модуля. Вместо `Foo.Bar` можно обращаться к модулю через `Bar`
alias Foo.Bar, as: Bar
# Подключение модуля `Foo` для использования его макросов
require Foo
# Импорт функций из модуля `Foo`, чтобы вызывать их без префикса `Foo`
import Foo
# Вызов кода из модуля `Foo` в качестве точки расширения
use Foo
Мы рассмотрим их подробно. Помните, что первые три называются директивами, потому что имеют лексическую область видимости, в то время как use – это точка расширения.
Директива alias
Директива alias позволяет нам устанавливать псевдонимы для любых имён модулей.
Представьте модуль, который использует особую форму списка из Math.List. Директива alias позволяет ссылаться на Math.List по имени List без указания модуля:
defmodule Stats do
alias Math.List, as: List
# In the remaining module definition List expands to Math.List.
end
Оригинальный List также доступен внутри Stats по полному имени Elixir.List.
Обратите внимание. Все модули, определённые в Эликсире, определены в главном пространстве имён
Elixir. Однако, для удобства, вы можете опуститьElixir.при обращении к ним.
Псевдонимы часто используются для сокращений. Вызов директивы alias без опции :as автоматически устанавливает псевдоним для последней части имени модуля, например:
alias Math.List
То же самое, что:
alias Math.List, as: List
Помните, что alias имеет лексическую область видимости, поэтому вы можете устанавливать псевдонимы внутри функций:
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
В примере выше, с момента, когда мы вызвали директиву alias внутри функции plus/2, псевдоним будет работать внутри этой функции. При этом он не будет доступен внутри функции minus/2.
Директива require
Эликсир предоставляет макросы, как механизм для метапрограммирования (написания кода, который генерирует код). Макросы генерируют код во время компиляции.
Публичные функции в модулях доступны глобально, но для использования макросов, вам нужно зарегистрировать модуль, в котором они определены.
iex> Integer.is_odd(3)
** (UndefinedFunctionError) function Integer.is_odd/1 is undefined or private. However there is a macro with the same name and arity. Be sure to require Integer if you intend to invoke this macro
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
В Эликсире функция Integer.is_odd/1 объявлена как макрос, поэтому её можно использовать в качестве охранного условия. Это значит, что для её вызова нужно подключить с помощью require модуль Integer.
Обратите внимание, что как и alias, директива require имеет лексическую область видимости. Мы поговорим подробнее о макросах в следующей главе.
Директива import
Директива import используется для облегчения доступа к функциям или макросам из другого модуля без использования полного имени. Например, если мы хотим использовать фукнцию duplicate/2 из модуля List несколько раз, мы можем её импортировать:
iex> import List, only: [duplicate: 2]
List
iex> duplicate :ok, 3
[:ok, :ok, :ok]
В данном случае, мы импортируем только функцию duplicate с арностью 2 из модуля List. Хотя опция :only необязательна, её использование рекомендовано во избежание импорта всех функций из модуля в текущее пространство имён. Опция :except может также быть передана для импорта всех функций, кроме указанных.
Директива import также поддерживает ключевые слова :macros и :functions для передачи в опцию :only. Например, для импорта всех макросов можно написать:
import Integer, only: :macros
Или для импорта всех функций:
import Integer, only: :functions
Помните, что директива import также имеет лексическую область видимости. Это значит, что мы можем импортировать указанные макросы и функции внутри определения функций:
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
В примере выше, импортированная List.duplicate/2 видна только внутри функции some_function. Функция duplicate/2 не будет доступна в других функциях этого модуля (или любого другого).
Обратите внимание, что директива import модуля автоматически производит его require.
Макрос use
Макрос use часто используется разработчиками для добавления внешней функциональности в текущую лексическую область видимости, зачастую, в модуль.
Например, для написания тестов с использованием фреймворка ExUnit, разработчику нужен модуль ExUnit.Case:
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
По сути, макрос use выполняет директиву require и совершает обратный вызов __using__/1 для него, позволяя модулю внедрить некоторый код в текущий контекст. В целом, следующий модуль:
defmodule Example do
use Feature, option: :value
end
компилируется в
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
Понимание псевдонимов
К этому моменту, вы, должно быть, задаётесь вопросом: чем являются псевдонимы в Эликсире и как они представлены?
Псевдоним в Эликсире – идентификатор, начинающийся с большой буквы (например, String, Keyword и т. д.), который конвертируется в атом во время компиляции. Например, псевдоним String переводится по умолчанию в атом :"Elixir.String":
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true
Используя директиву alias/2, мы изменяем атом, в который разворачивается псевдоним.
Псевдонимы разворачиваются в атомы, потому что модули виртуальной машины Эрланга (и, соответственно, Эликсира) всегда представлены как атомы. Например, вот механизм вызова модулей Эрланга:
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]
Вложенность модулей
Теперь, когда мы поговорили о псевдонимах, мы можем поговорить о вложенности, и как она работает в Эликсире. Рассмотрим следующий пример:
defmodule Foo do
defmodule Bar do
end
end
Пример выше определит два модуля: Foo и Foo.Bar. Последний доступен как Bar внутри Foo, т. к. они находятся в одной лексической области видимости. Код выше ровно то же самое, что:
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
Если позже модуль Bar будет перемещён наружу из определения модуля Foo, к нему придётся обращаться по полному имени Foo.Bar или создать псевдоним, используя директиву alias, о которой мы говорили выше.
Помните: в Эликсире вам не нужно определять модуль Foo перед тем как определить модуль Foo.Bar, потому что язык переводит все имена модулей в атомы. Вы можете определить произвольно-вложенные модули без определения модулей в цепочке (например, Foo.Bar.Baz без определения Foo или Foo.Bar в начале).
Как мы увидим в более поздних главах, псевдонимы также играют ключевую роль в макросах, гарантируя их корректность.
Множественные alias/import/require/use
Начиная с Эликсира версии 1.2, можно сделать alias, import или require нескольких модулей одновременно. Это оказывается полезным, когда мы начинаем писать вложенные модули, что часто используется при написании приложений на Эликсире. Например, представьте, что у вас есть приложение, где все модули вложены в MyApp, вы можете сделать alias модулей MyApp.Foo, MyApp.Bar и MyApp.Baz одновременно:
alias MyApp.{Foo, Bar, Baz}
На этом мы закончим знакомство с модулями Эликсира. Последняя глава посвящена атрибутам модулей.