Исходный код 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 не будет доступна в любой другой функции этого модуля (или любого другого модуля).
Хотя import могут быть полезны для фреймворков и библиотек, чтобы создавать абстракции, разработчики обычно предпочитают использовать 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.17.2/alias-require-and-import.html