Spec-Zone.ru › Elixir 1.10

Код

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

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

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

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

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

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

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

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

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

Трекеры компиляции

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 вызова, module — вызываемый модуль, за которым следуют name и arity.

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

Опция компилятора :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()

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

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_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) устаревшая

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

prepend_path(path)

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

purge_compiler_modules()

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

put_compiler_option(key, value)

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

require_file(file, relative_to \\ nil)

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

required_files()

Список всех требуемых файлов.

string_to_quoted(string, opts \\ [])

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

string_to_quoted!(string, opts \\ [])

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

unrequire_files(files)

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

Типы

binding()

Specs

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

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

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

Функции

append_path(path)

Характеристики

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()

Характеристики

available_compiler_options() :: [atom()]

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

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

Примеры

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

compile_file(file, relative_to \\ nil)

Характеристики

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

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

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

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

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

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

compile_quoted(quoted, file \\ "nofile")

Характеристики

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

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

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

compile_string(string, file \\ "nofile")

Характеристики

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

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

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

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

compiler_options()

Характеристики

compiler_options() :: map()

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

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

Примеры

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

compiler_options(opts)

Характеристики

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

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

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

Примеры

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

delete_path(path)

Характеристики

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)

Характеристики

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

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

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

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

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

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

См. ensure_loaded/1 для получения дополнительной информации о загрузке модулей и о том, когда использовать ensure_loaded/1 или ensure_compiled/1.

ensure_loaded(module)

Характеристики

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

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

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

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

Загрузка кода в виртуальной машине Erlang

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

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

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

ensure_compiled/1

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

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

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

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

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

Примеры

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

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

ensure_loaded?(module)

Характеристики

ensure_loaded?(module()) :: boolean()

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

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

Примеры

iex> Code.ensure_loaded?(Atom)
true

eval_file(file, relative_to \\ nil)

Характеристики

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 \\ [])

Характеристики

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

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

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

См. eval_string/3 для описания binding и параметров.

Примеры

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

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

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

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

Характеристики

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

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

Аргумент binding — список привязок переменных. Аргумент opts — список параметров среды.

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

Параметры

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

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

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

Кроме того, можно настроить следующие значения области видимости:

  • :aliases — список кортежей с псевдонимом и его целевым объектом

  • :requires — список необходимых модулей

  • :functions — список кортежей, где первый элемент — модуль, а второй — список импортированных имён функций и их арности; список имён функций и арности должен быть отсортирован

  • :macros — список кортежей, где первый элемент — модуль, а второй — список импортированных имён макросов и их арности; список имён макросов и арности должен быть отсортирован

Обратите внимание, что установка любого из вышеперечисленных значений переопределяет значения по умолчанию Elixir. Например, установка :requires на [] больше не будет автоматически требовать модуль Kernel. Аналогично, установка :macros больше не будет автоматически импортировать макросы Kernel, такие как Kernel.if/2, Kernel.SpecialForms.case/2 и т. д.

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

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

Примеры

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

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

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

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

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

fetch_docs(module_or_path)

Характеристики

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 | :lfe | :alpaca | atom(),
     doc_content: %{required(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 \\ [])

Характеристики

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

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

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

format_string!(string, opts \\ [])

Характеристики

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

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

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

Параметры

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

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

  • :line_length — длина строки, к которой следует стремиться при форматировании документа. По умолчанию 98. Обратите внимание, что это значение используется как ориентир, но не навязывается форматировщиком, так как иногда требуется вмешательство пользователя. См. раздел «Запуск форматировщика»

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

  • :rename_deprecated_at — переименовать все известные устаревшие функции в данной версии на их не устаревшие аналоги. Ожидает корректную Version, которая обычно является минимальной версией Elixir, поддерживаемой проектом.

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

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

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

Во-первых, форматировщик по умолчанию никогда не изменяет семантики кода. Это означает, что входное и выходное AST эквивалентны. Допускается опциональное поведение, такое как :rename_deprecated_at, которое может нарушить это гарантию.

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

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

Запуск форматировщика

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

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

Давайте рассмотрим несколько примеров. Код ниже:

"this is a very long string ... #{inspect(some_value)}"

может быть отформатирован как:

"this is a very long string ... #{
  inspect(some_value)
}"

Это происходит потому, что единственное место, где форматировщик может вставить новую строку без изменения семантики кода, находится в интерполяции. В таких сценариях мы рекомендуем разработчикам непосредственно изменять код. Здесь мы можем использовать оператор бинарного конкатенации <>/2:

"this is a very long string " <>
  "... #{inspect(some_value)}"

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

Аналогичный пример — когда форматировщик разбивает определение функции на несколько разделов:

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

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

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

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

  • Незначительные цифры в числах сохраняются как есть. Однако форматировщик всегда вставляет нижние подчеркивания для десятичных чисел с более чем 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

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

get_compiler_option(key)

Характеристики

get_compiler_option(atom()) :: term()

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

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

Примеры

Code.get_compiler_option(:debug_info)
#=> true

get_docs(module, kind)

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

Характеристики

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)

Характеристики

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

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

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

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

Примеры

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

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

purge_compiler_modules()

Характеристики

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)

Характеристики

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

require_file(file, relative_to \\ nil)

Характеристики

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()

Характеристики

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 \\ [])

Характеристики

string_to_quoted(List.Chars.t(), keyword()) ::
  {:ok, Macro.t()} | {:error, {line :: pos_integer(), term(), term()}}

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

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

Опции

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

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

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

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

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

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

  • :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

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

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

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

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

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

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

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

string_to_quoted!(string, opts \\ [])

Характеристики

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

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

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

См. string_to_quoted/2 для информации об опциях.

unrequire_files(files)

Характеристики

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())

# Notice 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.10.4/Code.html

Spec-Zone.ru

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