Исходный код alias, require, import и use
Для обеспечения повторного использования программного обеспечения 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.18.1/alias-require-and-import.html