Директивы alias, require и import

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

На этом мы закончим знакомство с модулями Эликсира. Последняя глава посвящена атрибутам модулей.

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