Исходный код alias, require и import
Для повышения повторного использования программного обеспечения Elixir предоставляет три директивы (alias, require и import) плюс макрос, называемый use, краткое описание которых приведено ниже:
# Alias the module so it can be called as Bar instead of Foo.Bar alias Foo.Bar, as: Bar # Require the module in order to use its macros require Foo # Import functions from Foo so they can be called without the `Foo.` prefix import Foo # Invokes the custom code defined in Foo as an extension point 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пространства имён, такого какElixir.String. Однако для удобства вы можете опустить "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, псевдоним будет действовать только внутри функции plus/2. minus/2 не будет затронут вообще.
require
Elixir предоставляет макросы как механизм метапрограммирования (написание кода, генерирующего код). Макросы расширяются во время компиляции.
Общедоступные функции в модулях доступны глобально, но для использования макросов необходимо их предварительно подключить, требуя модуль, в котором они определены.
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
(elixir) Integer.is_odd(3)
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
В Elixir, Integer.is_odd/1 определён как макрос, чтобы он мог быть использован как условие. Это означает, что для вызова Integer.is_odd/1 необходимо предварительно подключить модуль 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 также имеет лексическую область видимости. Это означает, что мы можем импортировать конкретные макросы или функции внутри определений функций:
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
В приведённом выше примере импортированный List.duplicate/2 виден только внутри этой конкретной функции. duplicate/2 не будет доступен ни в какой другой функции в этом модуле (или в любом другом модуле).
Хотя псевдонимы могут быть полезны для фреймворков и библиотек для построения абстракций, разработчики в целом должны отдавать предпочтение alias перед import в собственных кодовых базах, так как псевдонимы делают происхождение вызываемой функции более понятным.
use
Макрос use часто используется как точка расширения. Это означает, что при use модуля FooBar, вы разрешаете этому модулю вставлять любой код в текущий модуль, такой как импорт самого себя или других модулей, определение новых функций, установка состояния модуля и т. д.
Например, для написания тестов с использованием фреймворка ExUnit разработчик должен использовать модуль ExUnit.Case:
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
За кулисами use подключает данный модуль и затем вызывает обратный вызов __using__/1 на нём, что позволяет модулю вставить некоторый код в текущий контекст. Некоторые модули (например, указанный выше ExUnit.Case, а также Supervisor и GenServer) используют этот механизм для заполнения вашего модуля некоторым базовым поведением, которое ваш модуль предназначен для переопределения или дополнения.
В общем случае, следующий модуль:
defmodule Example do use Feature, option: :value end
компилируется в
defmodule Example do require Feature Feature.__using__(option: :value) end
Поскольку use позволяет выполнять любой код, мы не можем точно знать побочные эффекты использования модуля без чтения его документации. Поэтому используйте эту функцию с осторожностью и только в строго необходимых случаях. Не используйте use там, где можно обойтись import или alias.
Понимание псевдонимов
На этом этапе вы, возможно, задаётесь вопросом: что такое псевдоним в Elixir и как он представлен?
Псевдоним в Elixir — это идентификатор с большой буквы (например, String, Keyword и т. д.), который преобразуется в атом во время компиляции. Например, псевдоним String по умолчанию преобразуется в атом :"Elixir.String":
iex> is_atom(String) true iex> to_string(String) "Elixir.String" iex> :"Elixir.String" == String true
Используя директиву alias/2, мы изменяем атом, к которому расширяется псевдоним.
Псевдонимы расширяются до атомов, потому что в виртуальной машине Erlang (и, следовательно, Elixir) модули всегда представлены атомами:
iex> List.flatten([1, [2], 3]) [1, 2, 3] iex> :"Elixir.List".flatten([1, [2], 3]) [1, 2, 3]
Вот как мы вызываем модули Erlang:
iex> :lists.flatten([1, [2], 3]) [1, 2, 3]
Вложенные модули
Теперь, когда мы поговорили о псевдонимах, мы можем поговорить о вложенности и о том, как это работает в Elixir. Рассмотрим следующий пример:
defmodule Foo do defmodule Bar do end end
В приведённом выше примере будут определены два модуля: Foo и Foo.Bar. Второй можно получить как Bar внутри Foo при условии, что они находятся в одной и той же области видимости.
Если позже модуль Bar перемещается за пределы определения модуля Foo, он должен быть упомянут по полному имени (Foo.Bar) или для него должен быть установлен псевдоним с помощью директивы alias, о которой говорилось выше.
Примечание: в Elixir вам не нужно определять модуль Foo до того, как вы сможете определить модуль Foo.Bar, так как они фактически независимы. Вышесказанное также можно записать как:
defmodule Foo.Bar do end defmodule Foo do alias Foo.Bar # Can still access it as `Bar` end
Установка псевдонима для вложенного модуля не приводит родительские модули в область видимости. Рассмотрим следующий пример:
defmodule Foo do
defmodule Bar do
defmodule Baz do
end
end
end
alias Foo.Bar.Baz
# The module `Foo.Bar.Baz` is now available as `Baz`
# However, the module `Foo.Bar` is *not* available as `Bar`
Как мы увидим в последующих главах, псевдонимы также играют важную роль в макросах, чтобы гарантировать их гигиеничность.
Многократный alias/import/require/use
Можно alias, import, require, или use сразу несколько модулей. Это особенно полезно при начале вложения модулей, что очень распространено при создании приложений Elixir. Например, представьте себе приложение, где все модули вложены в MyApp, вы можете установить псевдонимы для модулей MyApp.Foo, MyApp.Bar и MyApp.Baz одновременно следующим образом:
alias MyApp.{Foo, Bar, Baz}
На этом мы завершаем наш обзор модулей Elixir. Следующая тема — атрибуты модулей.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/alias-require-and-import.html