Spec-Zone.ru › Elixir 1.13

Код

Утилиты для управления компиляцией кода, оценкой кода и загрузкой кода.

Этот модуль дополняет модуль Erlang's :code, добавляя поведение, специфичное для Elixir. Почти все функции в этом модуле имеют глобальные побочные эффекты на поведение Elixir.

Работа с файлами

Этот модуль содержит три функции для компиляции и оценки файлов. Вот краткое описание их и поведения:

  • require_file/2 - компилирует файл и отслеживает его имя. Он не компилирует файл повторно, если он уже был запрошен.

  • compile_file/2 - компилирует файл без отслеживания его имени. Компилирует файл многократно при каждом вызове.

  • eval_file/2 - оценивает содержимое файла без отслеживания его имени. Возвращает результат последнего выражения в файле, а не модули, определённые в нём. Оцениваемые файлы не запускают отслеживатели компиляции, описанные в следующем разделе.

Вкратце, первую функцию следует использовать, когда вам нужно отслеживать файлы, обрабатываемые системой, чтобы избежать повторной компиляции одного и того же файла. Это часто используется в скриптах.

compile_file/2 необходимо использовать, когда вы заинтересованы в модулях, определённых в файле, без отслеживания. eval_file/2 следует использовать, когда вас интересует результат оценки файла, а не модули, которые он определяет.

Указанные выше функции работают с исходным кодом Elixir. Если вы хотите работать с модулями, скомпилированными в байт-код, имеющими расширение .beam и обычно находящимися в каталоге _build проекта Mix, см. функции в модуле Erlang's :code.

Загрузка кода в Erlang VM

Erlang имеет два режима загрузки кода: интерактивный и встроенный.

По умолчанию Erlang VM работает в интерактивном режиме, где модули загружаются по мере необходимости. Встроенном режиме происходит обратное: все модули должны быть загружены заранее или явно.

Вы можете использовать ensure_loaded/1 (а также ensure_loaded?/1 и ensure_loaded!/1), чтобы проверить, загружен ли модуль перед его использованием и принять соответствующие меры.

ensure_compiled/1 и ensure_compiled!/1

Elixir также включает функции ensure_compiled/1 и ensure_compiled!/1, которые являются надмножеством ensure_loaded/1.

Поскольку компиляция в Elixir происходит параллельно, в некоторых ситуациях вам может потребоваться использовать модуль, который ещё не был скомпилирован, поэтому его нельзя загрузить.

При вызове ensure_compiled/1 и ensure_compiled!/1 компиляция вызывающего кода приостанавливается до тех пор, пока модуль не станет доступным. Обратите внимание на различие между ensure_compiled/1 и ensure_compiled!/1: если вы используете ensure_compiled!/1, вы указываете компилятору, что можете продолжить только если данный модуль доступен.

Если вы используете Code.ensure_compiled/1, вы предполагаете, что можете продолжить без модуля, и поэтому Elixir может вернуть {:error, :unavailable} в случаях, когда модуль ещё не доступен (но может стать доступным позже).

По этим причинам разработчики обычно используют Code.ensure_compiled!/1. В частности, не делайте так:

case Code.ensure_compiled(module) do
  {:module, _} -> module
  {:error, _} -> raise ...
end

Наконец, обратите внимание, что ensure_compiled!/1 нужно только для проверки модулей, определённых в текущем проекте. Он не относится к модулям из зависимостей, так как зависимости всегда компилируются заранее.

В большинстве случаев ensure_loaded/1 достаточно. ensure_compiled!/1 необходимо использовать в редких случаях, обычно связанных с макросами, которые нуждаются в вызове модуля для получения информации о обратных вызовах. Использование ensure_compiled/1 ещё реже.

Отслеживатели компиляции

Elixir поддерживает отслеживатели компиляции, которые позволяют модулям наблюдать за конструкциями, обрабатываемыми компилятором Elixir при компиляции файлов. Отслеживатель — это модуль, реализующий функцию trace/2. Функция получает имя события в качестве первого аргумента и Macro.Env в качестве второго, и должна возвращать :ok. Крайне важно, чтобы отслеживатель выполнял как можно меньше работы синхронно и передавал основную часть работы в отдельный процесс. Медленные отслеживатели замедлят компиляцию.

Вы можете настроить список отслеживателей с помощью put_compiler_option/2. Доступные события для отслеживателей:

  • :start - (с версии v1.11.0) вызывается всякий раз, когда компилятор начинает отслеживать новый лексический контекст, например, новый файл. Имейте в виду, что компилятор работает параллельно, поэтому несколько файлов могут вызывать :start и выполняться одновременно. Значение lexical_tracker среды макросов, хотя и неявное, может использоваться для уникальной идентификации среды.

  • :stop - (с версии v1.11.0) вызывается всякий раз, когда компилятор прекращает отслеживание нового лексического контекста, например, нового файла.

  • {:import, meta, module, opts} - отслеживается всякий раз, когда module импортируется. meta — метаданные AST импорта, а opts — опции импорта.

  • {:imported_function, meta, module, name, arity} и {:imported_macro, meta, module, name, arity} - отслеживается всякий раз, когда вызывается импортированная функция или макрос. meta — метаданные вызова AST, module — модуль, из которого происходит импорт, за которым следуют name и arity импортированной функции/макроса.

  • {:alias, meta, alias, as, opts} - отслеживается всякий раз, когда alias алиасируется на as. meta — метаданные AST алиаса, а opts — опции алиаса.

  • {:alias_expansion, meta, as, alias} отслеживается всякий раз, когда происходит расширение алиаса для ранее определённого alias, т. е. когда пользователь пишет as, которое расширяется до alias. meta — метаданные AST расширения алиаса.

  • {:alias_reference, meta, module} - отслеживается всякий раз, когда в коде есть алиас, т. е. всякий раз, когда пользователь пишет MyModule.Foo.Bar в коде, независимо от того, был он расширен или нет.

  • {:require, meta, module, opts} - отслеживается всякий раз, когда module запрашивается. meta — метаданные AST запроса, а opts — опции запроса.

  • {:struct_expansion, meta, module, keys} - отслеживается всякий раз, когда структура module расширяется. meta — метаданные AST структуры, а keys — используемые ключи расширения.

  • {:remote_function, meta, module, name, arity} и {:remote_macro, meta, module, name, arity} - отслеживается всякий раз, когда ссылаются на удалённую функцию или макрос. meta — метаданные вызова AST, module — вызываемый модуль, за которым следуют name и arity.

  • {:local_function, meta, name, arity} и {:local_macro, meta, name, arity} - отслеживается всякий раз, когда ссылаются на локальную функцию или макрос. meta — метаданные вызова AST, за которыми следуют name и arity.

  • {:compile_env, app, path, return} - отслеживается всякий раз, когда вызываются Application.compile_env/3 или Application.compile_env!/2. app — атом, path — список ключей для обхода в среде приложения, а return — либо {:ok, value}, либо :error.

  • {:on_module, bytecode, :none} - (с версии v1.11.0) отслеживается всякий раз, когда определяется модуль. Это эквивалентно обратной функции @after_compile и вызывается после любого @after_compile в данном модуле. Третий элемент в настоящее время :none, но в будущем он может содержать больше метаданных. Лучше его игнорировать на данный момент.

Опция компилятора :tracers может быть объединена с опцией компилятора :parser_options для обогащения метаданных вышеупомянутых отслеживаемых событий.

Новые события могут добавляться в любое время в будущем, поэтому рекомендуется, чтобы функция trace/2 имела "всеобъемлющий" блок.

Ниже приведён пример отслеживателя, который выводит все удалённые вызовы функций:

defmodule MyTracer do
  def trace({:remote_function, _meta, module, name, arity}, env) do
    IO.puts "#{env.file}:#{env.line} #{inspect(module)}.#{name}/#{arity}"
    :ok
  end

  def trace(_event, _env) do
    :ok
  end
end

Краткое описание

Типы

binding()

Список всех привязок переменных.

Функции

append_path(path)

Добавляет путь в конец списка путей кода Erlang VM.

available_compiler_options()

Возвращает список всех доступных параметров компилятора.

can_await_module_compilation?()

Возвращает true, если текущий процесс может ожидать компиляции модуля.

compile_file(file, relative_to \\ nil)

Компилирует указанный файл.

compile_quoted(quoted, file \\ "nofile")

Компилирует цитированное выражение.

compile_string(string, file \\ "nofile")

Компилирует заданную строку.

compiler_options()

Получает все параметры компиляции из сервера кода.

compiler_options(opts)

Хранит все заданные параметры компиляции.

delete_path(path)

Удаляет путь из списка путей кода Erlang VM. Это список каталогов, используемых Erlang VM для поиска кода модуля.

ensure_compiled!(module)

Обеспечивает компиляцию и загрузку указанного модуля.

ensure_compiled(module)

Аналогично ensure_compiled!/1, но указывает, что вы можете продолжить без указанного модуля.

ensure_loaded!(module)

То же, что и ensure_loaded/1, но вызывает исключение, если модуль не может быть загружен.

ensure_loaded(module)

Обеспечивает загрузку указанного модуля.

ensure_loaded?(module)

Обеспечивает загрузку указанного модуля.

eval_file(file, relative_to \\ nil)

Вычисляет указанный файл.

eval_quoted(quoted, binding \\ [], opts \\ [])

Вычисляет цитируемое содержимое.

eval_string(string, binding \\ [], opts \\ [])

Вычисляет содержимое, заданное string.

fetch_docs(module_or_path)

Возвращает документацию для заданного модуля или пути к файлу .beam.

format_file!(file, opts \\ [])

Форматирует файл.

format_string!(string, opts \\ [])

Форматирует предоставленный код string.

get_compiler_option(key)

Возвращает значение заданного параметра компилятора.

get_docs(module, kind) deprecated

Устаревшая функция для получения старого формата документации.

prepend_path(path)

Добавляет путь в начало списка путей кода Erlang VM.

purge_compiler_modules()

Очистка модулей компилятора.

put_compiler_option(key, value)

Сохраняет параметр компиляции.

quoted_to_algebra(quoted, opts \\ [])

Преобразует цитируемое выражение в документ алгебры, используя правила форматирования Elixir.

require_file(file, relative_to \\ nil)

Требует указанный file.

required_files()

Список всех необходимых файлов.

string_to_quoted!(string, opts \\ [])

Преобразует заданную строку в её цитированную форму.

string_to_quoted(string, opts \\ [])

Преобразует заданную строку в её цитированную форму.

string_to_quoted_with_comments!(string, opts \\ [])

Преобразует заданную строку в её цитированную форму и список комментариев.

string_to_quoted_with_comments(string, opts \\ [])

Преобразует заданную строку в её цитированную форму и список комментариев.

unrequire_files(files)

Удаляет файлы из списка необходимых файлов.

Типы

binding()Source

@type binding() :: [{atom() | tuple(), any()}]

Список всех привязок переменных.

Ключи привязки обычно являются атомами, но могут быть кортежами для переменных, определённых в другом контексте.

END_OF_DOCUMENT_MARKER

Функции

append_path(path)Source

@spec append_path(Path.t()) :: true | {:error, :bad_directory}

Добавляет путь в конец списка путей кода виртуальной машины Erlang.

Это список каталогов, которые виртуальная машина Erlang использует для поиска кода модулей.

Путь расширяется с помощью Path.expand/1 перед добавлением. Если такого пути не существует, возвращается ошибка.

Примеры

Code.append_path(".")
#=> true

Code.append_path("/does_not_exist")
#=> {:error, :bad_directory}

available_compiler_options()Source

@spec available_compiler_options() :: [atom()]

Возвращает список всех доступных опций компилятора.

Описание всех опций см. в put_compiler_option/2.

Примеры

Code.available_compiler_options()
#=> [:docs, :debug_info, ...]

can_await_module_compilation?()Source

@spec can_await_module_compilation?() :: boolean()

Возвращает true, если текущий процесс может ожидать завершения компиляции модуля.

При компиляции кода Elixir через Kernel.ParallelCompiler, который используется Mix и elixirc, вызов модуля, который еще не скомпилирован, заблокирует вызывающий код, пока модуль не станет доступным. Выполнение скриптов Elixir, например, передача имени файла в elixir, не ожидает завершения.

compile_file(file, relative_to \\ nil)Source

@spec compile_file(binary(), nil | binary()) :: [{module(), binary()}]

Компилирует указанный файл.

Принимает relative_to в качестве аргумента, чтобы указать расположение файла.

Возвращает список кортежей, где первый элемент — имя модуля, а второй — его байткод (как бинарное значение). В отличие от require_file/2, он не отслеживает имя файла скомпилированного файла.

Если вам нужно получить результат вычисления файла, а не модулей, определенных в нем, см. eval_file/2.

Для одновременной компиляции нескольких файлов см. Kernel.ParallelCompiler.compile/2.

compile_quoted(quoted, file \\ "nofile")Source

@spec compile_quoted(Macro.t(), binary()) :: [{module(), binary()}]

Компилирует выражение с использованием цитирования.

Возвращает список кортежей, где первый элемент — имя модуля, а второй — его байткод (как бинарное значение). В качестве второго аргумента можно указать file, который будет использован для отчета о предупреждениях и ошибках.

compile_string(string, file \\ "nofile")Source

@spec compile_string(List.Chars.t(), binary()) :: [{module(), binary()}]

Компилирует заданную строку.

Возвращает список кортежей, где первый элемент — имя модуля, а второй — его байткод (как бинарное значение). В качестве второго аргумента можно указать file, который будет использован для отчета о предупреждениях и ошибках.

Предупреждение: string может содержать любой код Elixir, и этот код может выполняться с теми же привилегиями, что и виртуальная машина Erlang: это означает, что такой код может представлять угрозу для системы (например, выполняя системные команды). Не используйте compile_string/2 с ненадежным вводом (например, строками, полученными из сети).

compiler_options()Source

@spec compiler_options() :: map()

Получает все опции компиляции из сервера кода.

Для получения отдельных опций см. get_compiler_option/1. Для описания всех опций см. put_compiler_option/2.

Примеры

Code.compiler_options()
#=> %{debug_info: true, docs: true, ...}

compiler_options(opts)Source

@spec compiler_options(Enumerable.t()) :: %{optional(atom()) => boolean()}

Сохраняет все заданные опции компиляции.

Для сохранения отдельных опций и описания всех опций см. put_compiler_option/2.

Примеры

Code.compiler_options()
#=> %{debug_info: true, docs: true, ...}

delete_path(path)Source

@spec delete_path(Path.t()) :: boolean()

Удаляет путь из списка путей кода виртуальной машины Erlang. Это список каталогов, которые виртуальная машина Erlang использует для поиска кода модулей.

Путь расширяется с помощью Path.expand/1 перед удалением. Если путь не существует, эта функция возвращает false.

Примеры

Code.prepend_path(".")
Code.delete_path(".")
#=> true

Code.delete_path("/does_not_exist")
#=> false

ensure_compiled!(module)Source

@spec ensure_compiled!(module()) :: module()

Обеспечивает компиляцию и загрузку указанного модуля.

Если модуль уже загружен, выполняется как операция без действия. Если модуль еще не скомпилирован, ensure_compiled!/1 останавливает компиляцию вызывающего кода, пока модуль, переданный в ensure_compiled!/1, не станет доступным или все файлы текущего проекта не будут скомпилированы. Если компиляция завершается, а модуль недоступен или находится в тупике, генерируется ошибка.

Поскольку эта функция приостанавливает компиляцию, используйте ее с осторожностью. В частности, избегайте использования для определения, какие модули присутствуют в системе. Чрезмерное использование этой функции также может привести к тупиковым ситуациям, когда два модуля одновременно проверяют, скомпилирован ли другой. Эта функция возвращает специфическую ошибку недоступности, когда невозможно успешно проверить, доступен ли модуль или нет.

Дополнительную информацию о загрузке кода см. в документации по модулю.

ensure_compiled(module)Source

@spec ensure_compiled(module()) ::
  {:module, module()}
  | {:error, :embedded | :badfile | :nofile | :on_load_failure | :unavailable}

Аналогично ensure_compiled!/1, но указывает, что можно продолжить без указанного модуля.

В то время как ensure_compiled!/1 указывает компилятору Elixir, что вы можете продолжить только при доступности указанного модуля, эта функция указывает, что вы можете продолжить компиляцию без указанного модуля.

Если загрузка модуля успешно выполняется, возвращается {:module, module}. В противном случае возвращается {:error, reason} с причиной ошибки. Если проверяемый модуль в настоящее время находится в тупике компиляции, функция возвращает {:error, :unavailable}. Недоступность не обязательно означает, что модуль не существует, просто что он в данный момент недоступен, но он (или может) станет доступен в будущем.

Поэтому, если вы можете продолжить только если модуль доступен, используйте ensure_compiled!/1 вместо этого. В частности, не делайте так:

case Code.ensure_compiled(module) do
  {:module, _} -> module
  {:error, _} -> raise ...
end

Дополнительную информацию о загрузке кода см. в документации по модулю.

ensure_loaded!(module)Source

@spec ensure_loaded!(module()) :: module()

Аналогично ensure_loaded/1, но генерирует исключение, если модуль не может быть загружен.

ensure_loaded(module)Source

@spec ensure_loaded(module()) ::
  {:module, module()}
  | {:error, :embedded | :badfile | :nofile | :on_load_failure}

Обеспечивает загрузку указанного модуля.

Если модуль уже загружен, выполняется как операция без действия. Если модуль еще не загружен, он пытается загрузить его.

Если загрузка модуля выполняется успешно, возвращается {:module, module}. В противном случае возвращается {:error, reason} с причиной ошибки.

Дополнительную информацию о загрузке кода см. в документации по модулю.

Примеры

iex> Code.ensure_loaded(Atom)
{:module, Atom}

iex> Code.ensure_loaded(DoesNotExist)
{:error, :nofile}

ensure_loaded?(module)Source

@spec ensure_loaded?(module()) :: boolean()

Обеспечивает загрузку указанного модуля.

Аналогично ensure_loaded/1, но возвращает true если модуль уже загружен или был успешно загружен. В противном случае возвращает false.

Примеры

iex> Code.ensure_loaded?(Atom)
true

eval_file(file, relative_to \\ nil)Source

@spec eval_file(binary(), nil | binary()) :: {term(), binding()}

Вычисляет указанный файл.

Принимает relative_to в качестве аргумента, чтобы указать расположение файла.

В то время как require_file/2 и compile_file/2 возвращают загруженные модули и их байткод, eval_file/2 просто вычисляет содержимое файла и возвращает результат вычисления и его привязку (ровно тот же результат, что и у eval_string/3).

eval_quoted(quoted, binding \\ [], opts \\ [])Source

@spec eval_quoted(Macro.t(), binding(), Macro.Env.t() | keyword()) ::
  {term(), binding()}

Вычисляет содержимое с использованием цитирования.

Предупреждение: Вызов этой функции внутри макроса считается плохой практикой, так как она попытается оценить значения во время выполнения на этапе компиляции. Аргументы макросов обычно преобразуются, удаляя цитирование в возвращаемые цитированные выражения (вместо вычисления).

См. eval_string/3 для описания binding и opts.

Примеры

iex> contents = quote(do: var!(a) + var!(b))
iex> {result, binding} = Code.eval_quoted(contents, [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2]

Для удобства вы можете передать __ENV__/0 в качестве аргумента opts и все параметры будут автоматически извлечены из текущей среды:

iex> contents = quote(do: var!(a) + var!(b))
iex> {result, binding} = Code.eval_quoted(contents, [a: 1, b: 2], __ENV__)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2]

eval_string(string, binding \\ [], opts \\ [])Source

@spec eval_string(List.Chars.t(), binding(), Macro.Env.t() | keyword()) ::
  {term(), binding()}

Вычисляет содержимое, заданное string.

Аргумент binding — это список привязок переменных. Аргумент opts — это список параметров в формате ключевых слов.

Предупреждение: string может быть любым кодом Elixir и будет выполняться с теми же привилегиями, что и Erlang VM: это означает, что такой код может нанести ущерб машине (например, выполнив системные команды). Не используйте eval_string/3 с недоверенным вводом (таким как строки, полученные из сети).

Параметры

Параметры могут быть:

  • :file — файл, который следует учитывать при вычислении

  • :line — строка, с которой начинается скрипт

Кроме того, вы также можете передать среду в качестве второго аргумента, чтобы вычисление происходило в этой среде. Однако, если вычисляемый код требует или компилирует другой файл, среда, переданная в эту функцию, не будет применяться к этим файлам.

Возвращает кортеж вида {value, binding}, где value — значение, возвращаемое при вычислении string. Если при вычислении string произошла ошибка, будет возбуждено исключение.

binding — список со всеми привязками переменных после вычисления string. Ключи привязки обычно являются атомами, но могут быть кортежами для переменных, определённых в другом контексте.

Примеры

iex> {result, binding} = Code.eval_string("a + b", [a: 1, b: 2], file: __ENV__.file, line: __ENV__.line)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2]

iex> {result, binding} = Code.eval_string("c = a + b", [a: 1, b: 2], __ENV__)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2, c: 3]

iex> {result, binding} = Code.eval_string("a = a + b", [a: 1, b: 2])
iex> result
3
iex> Enum.sort(binding)
[a: 3, b: 2]

Для удобства вы можете передать __ENV__/0 в качестве аргумента opts и все импорты, require и алиасы, определенные в текущей среде, будут автоматически перенесены:

iex> {result, binding} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2]

fetch_docs(module_or_path)Source

@spec fetch_docs(module() | String.t()) ::
  {:docs_v1, annotation, beam_language, format, module_doc :: doc_content,
   metadata, docs :: [doc_element]}
  | {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary()}}
when annotation: :erl_anno.anno(),
     beam_language: :elixir | :erlang | atom(),
     doc_content: %{optional(binary()) => binary()} | :none | :hidden,
     doc_element:
       {{kind :: atom(), function_name :: atom(), arity()}, annotation,
        signature, doc_content, metadata},
     format: binary(),
     signature: [binary()],
     metadata: map()

Возвращает документацию для заданного модуля или пути к файлу .beam.

При указании имени модуля, находит его код BEAM и считывает документацию из него.

При указании пути к файлу .beam файла, загружает документацию напрямую из этого файла.

Возвращает терм, хранящийся в блоке документации в формате, определенном в EEP 48 или {:error, reason} если блок документации недоступен.

Примеры

# Module documentation of an existing module
iex> {:docs_v1, _, :elixir, _, %{"en" => module_doc}, _, _} = Code.fetch_docs(Atom)
iex> module_doc |> String.split("\n") |> Enum.at(0)
"Atoms are constants whose values are their own name."

# A module that doesn't exist
iex> Code.fetch_docs(ModuleNotGood)
{:error, :module_not_found}

format_file!(file, opts \\ [])Source

@spec format_file!(
  binary(),
  keyword()
) :: iodata()

Форматирует файл.

См. format_string!/2 для получения дополнительной информации о форматировании кода и доступных параметрах.

format_string!(string, opts \\ [])Source

@spec format_string!(
  binary(),
  keyword()
) :: iodata()

Форматирует предоставленный код string.

Форматтер получает строку, представляющую код Elixir, и возвращает iodata, представляющую отформатированный код в соответствии с предопределёнными правилами.

Параметры

  • :file — файл, содержащий строку, используется для отчётности об ошибках

  • :line — строка, с которой начинается строка, используется для отчётности об ошибках

  • :line_length — длина строки, к которой следует стремиться при форматировании документа. По умолчанию 98. Это значение используется как руководство, но в некоторых ситуациях не применяется. Более подробная информация в разделе «Длина строки» ниже

  • :locals_without_parens — список ключевых слов с парами «имя-арность», которые следует сохранять без скобок, когда это возможно. Арность может быть атомом :*, что подразумевает все арности этого имени. Форматтер уже включает список функций, и этот параметр дополняет этот список.

  • :force_do_end_blocks (с версии v1.9.0) — когда true, преобразует все встроенные использования do: ..., else: ... и аналогичных блоков в блоки do-end. По умолчанию false. Обратите внимание, что этот параметр является конвергентным: если вы установите его в значение true, все ключевые слова будут преобразованы. Если вы установите его в false позже, блоки do-end не будут преобразованы обратно в ключевые слова.

Принципы проектирования

Форматтер разрабатывался на основе трёх принципов.

Во-первых, форматтер по умолчанию никогда не изменяет семантики кода. Это означает, что входное и выходное AST эквивалентны.

Второй принцип — минимальная конфигурация. Это облегчает внедрение форматтера, устраняя точки разногласий и гарантируя соблюдение единого стиля в сообществе в целом.

Форматтер не использует жёстко закодированные имена. Форматтер не будет вести себя особым образом, потому что функция называется defmodule, def или подобным образом. Этот принцип отражает цель Elixir — быть расширяемым языком, где разработчики могут расширять язык новыми конструкциями так, как будто они являются частью языка. Если необходимы изменения поведения на основе имени, это поведение должно быть настраиваемым, как, например, параметр :locals_without_parens.

Запуск форматтера

Форматтер пытается уместить как можно больше на одной строке и вставляет переводы строк, когда это невозможно.

В некоторых случаях это может привести к нежелательному форматированию. Поэтому некоторый код, сгенерированный форматтером, может не быть эстетически привлекательным и может потребовать явного вмешательства разработчика. Именно поэтому мы не рекомендуем слепо запускать форматтер в существующей базе кода. Вместо этого следует форматировать и проверять правильность каждого отформатированного файла.

Например, форматтер может разбить длинное определение функции на несколько разделов:

def my_function(
  %User{name: name, age: age, ...},
  arg1,
  arg2
) do
  ...
end

Хотя код выше полностью корректен, вы можете предпочесть сопоставлять переменные структуры внутри тела функции, чтобы сохранить определение на одной строке:

def my_function(%User{} = user, arg1, arg2) do
  %{name: name, age: age, ...} = user
  ...
end

В некоторых ситуациях можно воспользоваться тем, что форматтер не генерирует элегантный код, как подсказку для рефакторинга. Рассмотрим этот код:

def board?(board_id, %User{} = user, available_permissions, required_permissions) do
  Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
    required_permissions == Enum.to_list(MapSet.intersection(MapSet.new(required_permissions), MapSet.new(available_permissions)))
end

У кода выше очень длинные строки, и запуск форматтера не решит эту проблему. Фактически, форматтер может сделать более очевидным, что у вас сложные выражения:

def board?(board_id, %User{} = user, available_permissions, required_permissions) do
  Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
    required_permissions ==
      Enum.to_list(
        MapSet.intersection(
          MapSet.new(required_permissions),
          MapSet.new(available_permissions)
        )
      )
end

Рассматривайте такие случаи как подсказку для рефакторинга вашего кода:

def board?(board_id, %User{} = user, available_permissions, required_permissions) do
  Tracker.OrganizationMembers.user_in_organization?(user.id, board.organization_id) and
    matching_permissions?(required_permissions, available_permissions)
end

defp matching_permissions?(required_permissions, available_permissions) do
  intersection =
    required_permissions
    |> MapSet.new()
    |> MapSet.intersection(MapSet.new(available_permissions))
    |> Enum.to_list()

  required_permissions == intersection
end

Подводя итог: поскольку форматтер не может изменить семантику вашего кода, иногда необходимо настроить или переработать код, чтобы получить оптимальное форматирование. Чтобы лучше понять, как контролировать форматтер, в следующих разделах мы опишем случаи, когда форматтер сохраняет кодировку пользователя, и как контролировать многострочные выражения.

Длина строки

Ещё один момент, касающийся форматтера, заключается в том, что конфигурация :line_length является руководством. Во многих случаях форматтер не может разбить ваш код, что означает, что он будет превышать длину строки. Например, если у вас длинная строка:

"this is a very long string that will go over the line length"

Форматтер не знает, как её разбить, не изменив лежащую в основе синтаксическую структуру кода, поэтому вам придётся вмешаться:

"this is a very long string " <>
   "that will go over the line length"

Конкатенация строк позволяет коду поместиться на одной строке и также даёт форматтеру больше вариантов.

Это также может возникнуть в блоках do/end, где ключевое слово do (или ->) может превышать длину строки, так как форматтер не может вставить перевод строки удобочитаемым образом. Например, если сделать так:

case very_long_expression() do
end

И только ключевое слово do превышает длину строки, Elixir не сгенерирует это:

case very_long_expression()
do
end

Поэтому он предпочитает не трогать строку и оставить do выше предела длины строки.

Сохранение форматирования пользователя

Форматтер сохраняет формат входных данных в некоторых случаях. Они перечислены ниже:

  • Незначительные цифры в числах сохраняются как есть. Однако форматтер всегда вставляет нижние подчёркивания для десятичных чисел с более чем 5 цифрами и преобразует шестнадцатеричные цифры в верхний регистр.

  • Строки, списки символов, атомы и сигилы сохраняются как есть. Ни один символ не экранируется или убирается автоматически. Выбор разделителя также сохраняется из входных данных.

  • Переводы строк внутри блоков сохраняются, как в исходном коде, за исключением:

    1. выражений, занимающих несколько строк, которые всегда будут иметь пустую строку перед и после, и 2) пустые строки всегда объединяются в одну пустую строку
  • Выбор между ключевым словом :do и блоками do-end остается на усмотрение пользователя.

  • Списки, кортежи, бистроки, карты, структуры и вызовы функций будут разбиты на несколько строк, если за открывающей скобкой следует перевод строки и перед закрывающей скобкой стоит перевод строки.

  • Переводы строк перед определёнными операторами (такими как операторы конвейера) и перед другими операторами (такими как операторы сравнения).

Вышеперечисленные поведения не гарантированы. В будущем мы можем удалить или добавить новые правила. Цель документирования — обеспечить лучшее понимание ожиданий от форматтера.

Многострочные списки, карты, кортежи и т.п.

Вы можете принудительно сделать так, чтобы в списках, кортежах, бистроках, картах, структурах и вызовах функций находилось по одному элементу на строке, добавив перевод строки после открывающей скобки и перед закрывающей скобкой. Например:

[
  foo,
  bar
]

Если вокруг скобок нет переводов строк, форматтер попытается поместить всё на одной строке, так что фрагмент ниже

[foo,
 bar]

будет отформатирован как

[foo, bar]

Вы также можете принудительно сделать так, чтобы вызовы функций и ключевые слова отображались на нескольких строках, размещая каждый элемент на отдельной строке:

defstruct name: nil,
          age: 0

Форматтер сохранит код с одним ключевым элементом на строке. Чтобы этого избежать, просто объедините всё в одну строку.

Скобки и отсутствие скобок в вызовах функций

В Elixir есть два синтаксиса для вызовов функций: со скобками и без скобок. По умолчанию Elixir добавляет скобки ко всем вызовам, кроме:

  1. вызовов, имеющих блоки do-end
  2. локальных вызовов без скобок, где имя и арность локального вызова также указаны в :locals_without_parens (кроме вызовов с арностью 0, где компилятор всегда требует скобки).

Выбор скобок и их отсутствия также влияет на отступы. Когда вызов функции со скобками не помещается на одной строке, форматтер вставляет перевод строки вокруг скобок и отступы аргументов на два пробела:

some_call(
  arg1,
  arg2,
  arg3
)

С другой стороны, вызовы функций без скобок всегда отступают на длину самого вызова функции, например:

some_call arg1,
          arg2,
          arg3

Если последний аргумент является структурой данных, такой как карты и списки, а начало структуры данных помещается на одной строке с вызовом функции, отступ не происходит, что позволяет использовать код такого типа:

Enum.reduce(some_collection, initial_value, fn element, acc ->
  # code
end)

some_function_without_parens %{
  foo: :bar,
  baz: :bat
}

Комментарии к коду

Форматтер также обрабатывает комментарии к коду таким образом, чтобы гарантировать добавление пробела между началом комментария (#) и последующим символом.

Форматтер также перемещает все заключительные комментарии на предыдущую строку. Например, код ниже

hello #world

будет переписан как

# world
hello

Поскольку комментарии к коду обрабатываются отдельно от представления кода (AST), в некоторых ситуациях комментарии к коду считаются неоднозначными для форматтера кода. Например, комментарий в анонимной функции ниже

fn
  arg1 ->
    body1
    # comment

  arg2 ->
    body2
end

и в этом

fn
  arg1 ->
    body1

  # comment
  arg2 ->
    body2
end

считаются эквивалентными (вложенность отбрасывается вместе с большей частью форматирования пользователем). В таких случаях форматтер кода всегда будет форматировать в соответствии со вторым вариантом.

Переводы строк

Форматтер преобразует все переводы строк в коде из \r\n в \n.

get_compiler_option(key)Source

@spec get_compiler_option(atom()) :: term()

Возвращает значение заданного параметра компилятора.

Описание всех параметров см. в put_compiler_option/2.

Примеры

Code.get_compiler_option(:debug_info)
#=> true

get_docs(module, kind)Source

Данная функция устарела. Функция Code.get_docs/2 всегда возвращает nil, так как устаревшая документация больше не хранится в файлах BEAM. Используйте Code.fetch_docs/1 вместо этого.
@spec get_docs(module(), :moduledoc | :docs | :callback_docs | :type_docs | :all) ::
  nil

Устаревшая функция для получения старого формата документации.

Elixir v1.7 использует EEP 48, новый формат документации, предназначенный для совместного использования во всех языках BEAM. Старый формат, используемый функцией Code.get_docs/2, больше недоступен, и поэтому эта функция всегда возвращает nil. Используйте Code.fetch_docs/1 вместо этого.

prepend_path(path)Source

@spec prepend_path(Path.t()) :: true | {:error, :bad_directory}

Добавляет путь в начало списка путей к коду виртуальной машины Erlang.

Это список каталогов, которые Erlang VM использует для поиска модулей кода.

Путь расширяется с помощью Path.expand/1 перед добавлением в начало. Если такого пути не существует, возвращается ошибка.

Примеры

Code.prepend_path(".")
#=> true

Code.prepend_path("/does_not_exist")
#=> {:error, :bad_directory}

purge_compiler_modules()Source

@spec purge_compiler_modules() :: {:ok, non_neg_integer()}

Очистка модулей компилятора.

Компилятор использует временные модули для компиляции кода. Например, elixir_compiler_1, elixir_compiler_2, и так далее. В случае, если скомпилированный код хранит ссылки на анонимные функции или аналогичное, Elixir-компилятор может не суметь освободить эти модули, сохраняя излишнее количество кода в памяти, что в итоге может привести к модулям, таким как elixir_compiler_12345.

Эта функция очищает все модули, в настоящее время используемые компилятором, позволяя повторно использовать старые имена модулей компилятора. Если какие-либо процессы выполняют код из таких модулей, они также будут завершены.

Возвращает {:ok, number_of_modules_purged}.

put_compiler_option(key, value)Source

@spec put_compiler_option(atom(), term()) :: :ok

Сохраняет параметр компиляции.

Эти параметры являются глобальными, поскольку они хранятся сервером кода Elixir.

Доступные параметры:

  • :docs - когда true, сохранять документацию в скомпилированном модуле. По умолчанию true.

  • :debug_info - когда true, сохранять отладочную информацию в скомпилированном модуле. Это позволяет разработчику восстановить исходный код. По умолчанию true.

  • :ignore_module_conflict - когда true, перезаписывать уже определенные модули без выдачи ошибок. По умолчанию false.

  • :relative_paths - когда true, использовать относительные пути в цитируемых узлах, предупреждениях и ошибках, генерируемых компилятором. Обратите внимание, что отключение этого параметра не повлияет на предупреждения и ошибки во время выполнения. По умолчанию true.

  • :warnings_as_errors - заставляет компиляцию завершиться ошибкой при появлении предупреждений. По умолчанию false.

  • :no_warn_undefined (с версии 1.10.0) - список модулей и кортежей {Mod, fun, arity}, которые не будут генерировать предупреждения о том, что модуль или функция не существует во время компиляции. Передайте атом :all для отключения предупреждений обо всех неопределённых функциях. Это может быть полезно при динамической компиляции. По умолчанию [].

  • :tracers (с версии 1.10.0) - список трассировщиков (модулей), которые будут использоваться во время компиляции. См. документацию модуля для получения дополнительной информации. По умолчанию [].

  • :parser_options (с версии 1.10.0) - список параметров в формате ключ-значение, которые будут переданы парсеру при компиляции файлов. Он принимает те же параметры, что и string_to_quoted/2 (кроме параметров, изменяющих сам AST). Это можно использовать в сочетании с трассировщиком для получения локализованной информации о событиях, происходящих во время компиляции. По умолчанию [].

Всегда возвращает :ok. Возникает ошибка для недопустимых параметров.

Примеры

Code.put_compiler_option(:debug_info, true)
#=> :ok

quoted_to_algebra(quoted, opts \\ [])Source

@spec quoted_to_algebra(
  Macro.t(),
  keyword()
) :: Inspect.Algebra.t()

Преобразует выражение в виде цитируемого текста в документ алгебры с использованием правил форматирования Elixir.

Документ алгебры можно преобразовать в строку, вызвав:

doc
|> Inspect.Algebra.format(:infinity)
|> IO.iodata_to_binary()

Для высокоуровневой функции, выполняющей ту же задачу, см. Macro.to_string/1.

Особенности форматирования

Elixir AST не содержит метаданных для литералов, таких как строки, списки или кортежи с двумя элементами, что означает, что сгенерированный документ алгебры не будет учитывать все пользовательские настройки, и комментарии могут быть размещены неправильно. Для получения лучших результатов можно использовать параметры :token_metadata, :unescape и :literal_encoder для string_to_quoted/2, чтобы предоставить форматировщику дополнительную информацию:

[
  literal_encoder: &{:ok, {:__block__, &2, [&1]}},
  token_metadata: true,
  unescape: false
]

Это создаст AST, содержащий информацию, такую как do блоки начала и конца строк или разделители сигил, и, обернув литералы в блоки, теперь они могут содержать метаданные, такие как номер строки, разделитель строки и экранированные последовательности или форматирование целых чисел (например, 0x2a вместо 47). Однако, обратите внимание, что этот AST недействителен. Если вы его оцените, он не будет иметь тех же семантик, что и обычный Elixir AST из-за параметров :unescape и :literal_encoder. Тем не менее, эти параметры полезны, если вы выполняете манипуляции с исходным кодом, где важно сохранить пользовательские настройки и расположение комментариев.

Параметры

  • :comments - список комментариев, связанных с цитируемым выражением. По умолчанию []. Рекомендуется указать оба параметра :token_metadata и :literal_encoder для string_to_quoted_with_comments/2 для правильного размещения комментариев

  • :escape - если true, экранированные последовательности, такие как \n будут экранированы в \\n. Если параметр :unescape был установлен в false при использовании string_to_quoted/2, установка этого параметра в false предотвратит двойное экранирование последовательностей. По умолчанию true.

  • :locals_without_parens - список пар «имя-арность», которые должны сохраняться без скобок, когда это возможно. Арность может быть атомом :*, что подразумевает все арности этого имени. Форматировщик уже включает список функций, и этот параметр дополняет этот список.

require_file(file, relative_to \\ nil)Source

@spec require_file(binary(), nil | binary()) :: [{module(), binary()}] | nil

Загружает указанный file.

Принимает relative_to в качестве аргумента, чтобы указать, где находится файл. Если файл уже был загружен, require_file/2 ничего не делает и возвращает nil.

Обратите внимание, что если require_file/2 вызывается разными процессами одновременно, первый вызвавший require_file/2 процесс приобретает блокировку, и остальные будут заблокированы, пока файл не будет доступен. Это означает, что если require_file/2 вызывается более одного раза с заданным файлом, этот файл будет скомпилирован только один раз. Первый процесс, вызвавший require_file/2, получит список загруженных модулей, остальные получат nil.

См. compile_file/2, если вы хотите скомпилировать файл без отслеживания его имён. Наконец, если вы хотите получить результат вычисления файла, а не модулей, определённых в нём, см. eval_file/2.

Примеры

Если файл ещё не был загружен, возвращается список модулей:

modules = Code.require_file("eex_test.exs", "../eex/test")
List.first(modules)
#=> {EExTest.Compiled, <<70, 79, 82, 49, ...>>}

Если файл был загружен, возвращается nil:

Code.require_file("eex_test.exs", "../eex/test")
#=> nil

required_files()Source

@spec required_files() :: [binary()]

Выводит список всех загруженных файлов.

Примеры

Code.require_file("../eex/test/eex_test.exs")
List.first(Code.required_files()) =~ "eex_test.exs"
#=> true

string_to_quoted!(string, opts \\ [])Source

@spec string_to_quoted!(
  List.Chars.t(),
  keyword()
) :: Macro.t()

Преобразует данную строку в её цитируемую форму.

Возвращает AST, если преобразование успешно, иначе генерирует исключение. Исключение является TokenMissingError в случае отсутствия токена (обычно потому, что выражение неполное) и SyntaxError в противном случае.

См. string_to_quoted/2 для получения информации о параметрах.

string_to_quoted(string, opts \\ [])Source

@spec string_to_quoted(
  List.Chars.t(),
  keyword()
) ::
  {:ok, Macro.t()}
  | {:error, {location :: keyword(), binary() | {binary(), binary()}, binary()}}

Преобразует заданную строку в её форматированную строку.

Возвращает {:ok, quoted_form} при успехе, {:error, {meta, message_info, token}} в противном случае.

Параметры

  • :file - имя файла, которое будет сообщено в случае ошибок разбора. По умолчанию "nofile".

  • :line - начальная строка анализируемой строки. По умолчанию 1.

  • :column - (с версии v1.11.0) начальная колонка анализируемой строки. По умолчанию 1.

  • :columns - когда true, прикрепляет ключ :column к метаданным форматированной строки. По умолчанию false.

  • :unescape (с версии v1.10.0) - когда false, сохраняет экранированные последовательности. Например, "null byte\\t\\x00" останется без изменений, вместо преобразования в битовый литерал. Обратите внимание, если вы установите этот параметр в false, результирующее АСТ больше не будет валидным, но это может быть полезно для анализа/преобразования исходного кода, обычно в сочетании с quoted_to_algebra/2. По умолчанию true.

  • :existing_atoms_only - когда true, генерирует ошибку, если в ходе токенизации найдены несуществующие атомы. По умолчанию false.

  • :token_metadata (с версии v1.10.0) - когда true, включает метаданные, связанные с токенами, в АСТ выражения, такие как метаданные для do и end токенов, для закрывающих токенов, конца выражений, а также разделителей для сигилов. См. Macro.metadata/0. По умолчанию false.

  • :literal_encoder (с версии v1.10.0) - как закодировать литералы в АСТ. Это должна быть функция, которая принимает два аргумента, литерал и его метаданные, и возвращает {:ok, ast :: Macro.t} или {:error, reason :: binary}. Если вы вернете что-то отличное от самого литерала как term, АСТ больше не будет валиден. Этот параметр может быть полезен для текстового анализа исходного кода.

  • :static_atoms_encoder - статическая функция кодирования атомов, см. раздел "Функция :static_atoms_encoder" ниже. Обратите внимание, что этот параметр переопределяет поведение :existing_atoms_only для статических атомов, но :existing_atoms_only по-прежнему используется для динамических атомов, таких как атомы с интерполяцией.

  • :warn_on_unnecessary_quotes - если false, не выводит предупреждение, когда атомы, ключевые слова или вызовы имеют ненужные кавычки. По умолчанию true.

Macro.to_string/2

Обратный процесс преобразования строки в её форматированную строку — это Macro.to_string/2, который преобразует форматированную строку в строку/двоичное представление.

Функция кодирования статических атомов

Когда static_atoms_encoder: &my_encoder/2 передаётся в качестве аргумента, my_encoder/2 вызывается каждый раз, когда токенизатор нуждается в создании "статического" атома. Статические атомы — это атомы в АСТ, которые функционируют как псевдонимы, удалённые вызовы, локальные вызовы, имена переменных, обычные атомы и списки ключевых слов.

Функция кодирования получит имя атома (как двоичную строку) и список ключевых слов с текущим файлом, строкой и столбцом. Она должна вернуть {:ok, token :: term} | {:error, reason :: binary}.

Функция кодирования должна создать атом из заданной строки. Для создания валидного АСТ требуется вернуть {:ok, term}, где term — атом. Возможна и отдача результата, отличного от атома, но в этом случае АСТ больше не является "валидным", поскольку не может быть использован для компиляции или оценки кода Elixir. Сценарий использования — если вы хотите использовать парсер Elixir в пользовательском интерфейсе, но не хотите исчерпать таблицу атомов.

Функция кодирования атомов не вызывается для всех атомов, присутствующих в АСТ. Она не будет вызвана для следующих атомов:

  • операторы (:+, :-, и так далее)

  • ключевые слова синтаксиса (fn, do, else, и так далее)

  • атомы, содержащие интерполяцию (:"#{1 + 1} is two"), так как эти атомы создаются во время выполнения.

string_to_quoted_with_comments!(string, opts \\ [])Source

@spec string_to_quoted_with_comments!(
  List.Chars.t(),
  keyword()
) :: {Macro.t(), [map()]}

Преобразует заданную строку в её форматированную строку и список комментариев.

Возвращает АСТ и список комментариев при успехе, в противном случае генерирует исключение. Исключение — TokenMissingError в случае отсутствия токена (обычно потому, что выражение неполное), SyntaxError в противном случае.

См. string_to_quoted/2 для получения информации о параметрах.

string_to_quoted_with_comments(string, opts \\ [])Source

@spec string_to_quoted_with_comments(
  List.Chars.t(),
  keyword()
) ::
  {:ok, Macro.t(), [map()]} | {:error, {location :: keyword(), term(), term()}}

Преобразует заданную строку в её форматированную строку и список комментариев.

Эта функция полезна при выполнении текстовых изменений в исходном коде, сохраняя при этом информацию, такую как комментарии и положение литералов.

Возвращает {:ok, quoted_form, comments} при успехе, {:error, {line, error, token}} в противном случае.

Комментарии — это карты со следующими полями:

  • :line - Номер строки в исходном коде

  • :text - Полный текст комментария, включая предваряющий #

  • :previous_eol_count - Количество символов конца строки между комментарием и предыдущим узлом АСТ или комментарием

  • :next_eol_count - Количество символов конца строки между комментарием и следующим узлом АСТ или комментарием

См. string_to_quoted/2 для получения информации о параметрах.

Примеры

iex> Code.string_to_quoted_with_comments("""
...> :foo
...>
...> # Hello, world!
...>
...>
...> # Some more comments!
...> """)
{:ok, :foo, [
  %{line: 3, column: 1, previous_eol_count: 2, next_eol_count: 3, text: "# Hello, world!"},
  %{line: 6, column: 1, previous_eol_count: 3, next_eol_count: 1, text: "# Some more comments!"},
]}

iex> Code.string_to_quoted_with_comments(":foo # :bar")
{:ok, :foo, [
  %{line: 1, column: 6, previous_eol_count: 0, next_eol_count: 0, text: "# :bar"}
]}

unrequire_files(files)Source

@spec unrequire_files([binary()]) :: :ok

Удаляет файлы из списка требуемых файлов.

Модули, определённые в файлах, не удаляются; вызов этой функции только удаляет их из списка, позволяя потребовать их снова.

Примеры

# Require EEx test code
Code.require_file("../eex/test/eex_test.exs")

# Now unrequire all files
Code.unrequire_files(Code.required_files())

# Note that modules are still available
function_exported?(EExTest.Compiled, :before_compile, 0)
#=> true

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.13.4/Code.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API