Для повторного использования частей приложения в Эликсире есть три директивы: 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}
На этом мы закончим знакомство с модулями Эликсира. Последняя глава посвящена атрибутам модулей.