Spec-Zone.ru › Elixir 1.5

Kernel.SpecialForms

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

Мы определяем их в этом модуле. Некоторые из этих форм являются лексическими (например, alias/2, case/2 и т.д.). Макросы {} и <<>> также являются специальными формами, используемыми для определения кортежей и двоичных структур данных соответственно.

Этот модуль также документирует макросы, которые возвращают информацию об окружении компиляции Elixir, например (__ENV__/0, __MODULE__/0, __DIR__/0 и __CALLER__/0).

Наконец, он также документирует две специальные формы, __block__/1 и __aliases__/1, которые не предназначены для прямого вызова разработчиком, но они появляются в цитируемом содержимом, поскольку они необходимы в конструкциях Elixir.

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

Функции

%

Создаёт структуру

%{}

Создаёт карту

&(expr)

Захватывает или создаёт анонимную функцию

left . right

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

left :: right

Используется типами и битовыми строками для указания типов

<<args>>

Определяет новую битовую строку

left = right

Сопоставляет значение справа с шаблоном слева

^var

Доступ к уже связанной переменной в пунктах сопоставления. Также известен как оператор привязки

__CALLER__

Возвращает текущую вызывающую среду в виде структуры Macro.Env

__DIR__

Возвращает абсолютный путь к каталогу текущего файла в виде двоичной строки

__ENV__

Возвращает текущую информацию об окружении в виде структуры Macro.Env

__MODULE__

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

__aliases__(args)

Внутренняя специальная форма для хранения информации о псевдонимах

__block__(args)

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

alias(module, opts)

alias/2 используется для настройки псевдонимов, часто полезно с именами модулей

case(condition, clauses)

Сопоставляет данное выражение с данными пунктами

cond(clauses)

Вычисляет выражение, соответствующее первому пункту, который вычисляется в истинное значение

fn [clauses] end

Определяет анонимную функцию

for(args)

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

import(module, opts)

Импортирует функции и макросы из других модулей

quote(opts, block)

Получает представление любого выражения

receive(args)

Проверяет, есть ли сообщение, соответствующее заданным пунктам в почтовом ящике текущего процесса

require(module, opts)

Требует модуль для использования его макросов

super(args)

Вызывает переопределённую функцию при её переопределении с помощью Kernel.defoverridable/1

try(args)

Вычисляет заданные выражения и обрабатывает любые ошибки, выходы или броски, которые могли произойти

unquote(expr)

Унифицирует данное выражение изнутри макроса

unquote_splicing(expr)

Унифицирует данный список, расширяя его аргументы. Аналогично unquote/1

with(args)

Используется для объединения пунктов сопоставления

{args}

Создаёт кортеж

Функции

% (макрос)

Создаёт структуру.

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

Структуры обычно определяются с помощью макроса Kernel.defstruct/1:

defmodule User do
  defstruct name: "john", age: 27
end

Теперь структуру можно создать следующим образом:

%User{}

Внутри структура — это просто карта с ключом :__struct__, указывающим на модуль User:

%User{} == %{__struct__: User, name: "john", age: 27}

Структура также проверяет, являются ли заданные ключи частью определённой структуры. Приведённый ниже пример не пройдёт, так как нет ключа :full_name в структуре User:

%User{full_name: "john doe"}

Также доступна операция обновления, специфичная для структур:

%User{user | age: 28}

Вышеупомянутый синтаксис гарантирует, что заданные ключи будут валидны на этапе компиляции, и он гарантирует, что на этапе выполнения аргумент является структурой, в противном случае возникнет ошибка BadStructError.

Несмотря на то, что структуры являются картами, по умолчанию структуры не реализуют ни одного из протоколов, реализованных для карт. См. Kernel.defprotocol/2 для получения дополнительной информации о том, как структуры могут использоваться с протоколами для полиморфного диспетчера. Также см. Kernel.struct/2 и Kernel.struct!/2 для примеров создания и обновления структур динамически.

%{} (макрос)

Создаёт карту.

См. модуль Map для получения дополнительной информации о картах, их синтаксисе и способах доступа к ним и манипулирования ими.

Представление AST

Независимо от того, используется ли => или синтаксис ключевого слова, пары «ключ-значение» в картах всегда представляются в виде списка кортежей из двух элементов для простоты:

iex> quote do
...>   %{"a" => :b, c: :d}
...> end
{:%{}, [], [{"a", :b}, {:c, :d}]}

&(expr) (макрос)

Захватывает или создаёт анонимную функцию.

Захват

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

iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
true
iex> fun.("string")
false

В приведённом выше примере мы захватили Kernel.is_atom/1 как анонимную функцию, а затем вызвали её.

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

&local_function/1

Анонимные функции

Оператор захвата также может использоваться для частичного применения функций, где &1, &2 и т. д. могут использоваться в качестве заполнительных значений. Например:

iex> double = &(&1 * 2)
iex> double.(2)
4

Другими словами, &(&1 * 2) эквивалентно fn x -> x * 2 end. Ещё один пример с локальной функцией:

iex> fun = &is_atom(&1)
iex> fun.(:atom)
true

Оператор & может использоваться с более сложными выражениями:

iex> fun = &(&1 + &2 + &3)
iex> fun.(1, 2, 3)
6

А также со списками и кортежами:

iex> fun = &{&1, &2}
iex> fun.(1, 2)
{1, 2}

iex> fun = &[&1 | &2]
iex> fun.(1, 2)
[1 | 2]

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

# No placeholder, fails to compile.
&(:foo)

# Block expression, fails to compile.
&(&1; &2)

left . right (макрос)

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

Точка (.) в Elixir может использоваться для удалённых вызовов:

iex> String.downcase("FOO")
"foo"

В этом примере мы использовали . для вызова downcase в модуле String, передав "FOO" в качестве аргумента.

Точка может также использоваться для вызова анонимных функций:

iex> (fn(n) -> n end).(7)
7

в этом случае слева есть функция.

Мы также можем использовать точку для создания псевдонимов:

iex> Hello.World
Hello.World

В этот раз мы объединили два псевдонима, определив конечный псевдоним Hello.World.

Синтаксис

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

iex> Kernel.Sample
Kernel.Sample

iex> Kernel.length([1, 2, 3])
3

iex> Kernel.+(1, 2)
3

iex> Kernel."length"([1, 2, 3])
3

iex> Kernel.'+'(1, 2)
3

Обратите внимание, что Kernel."FUNCTION_NAME" будет обрабатываться как удалённый вызов, а не как псевдоним. Этот выбор был сделан, чтобы каждый раз, когда используются одинарные или двойные кавычки, мы имели удалённый вызов независимо от содержимого кавычек. Это решение также отражено в цитируемых выражениях, обсуждаемых ниже.

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

iex> negate = fn(n) -> -n end
iex> negate.(7)
-7

Цитируемое выражение

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

iex> quote do
...>   String.downcase("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}
END_OF_DOCUMENT_MARKER ```

Заметим, что у нас есть вложенная кортеж, содержащая атом :. , представляющий точку как первый элемент:

{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}

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

iex> quote do
...>   String."downcase"("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}

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

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

iex> quote do
...>   negate.(0)
...> end
{{:., [], [{:negate, [], __MODULE__}]}, [], [0]}

Когда правая часть является псевдонимом (т.е. начинается с заглавной буквы), мы получаем вместо этого:

iex> quote do
...>   Hello.World
...> end
{:__aliases__, [alias: false], [:Hello, :World]}

Мы более подробно рассмотрим псевдонимы в документации специальной формы __aliases__/1.

Разъяснение

Мы также можем использовать unquote для генерации удалённого вызова в цитируемом выражении:

iex> x = :downcase
iex> quote do
...>   String.unquote(x)("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]}

Аналогично Kernel."FUNCTION_NAME", unquote(x) всегда будет генерировать удалённый вызов независимо от значения x. Для генерации псевдонима через цитируемое выражение необходимо использовать Module.concat/2:

iex> x = Sample
iex> quote do
...>   Module.concat(String, unquote(x))
...> end
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
 [{:__aliases__, [alias: false], [:String]}, Sample]}

left :: right (макрос)

Используется типами и битовыми строками для указания типов.

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

@type number :: integer | float
@spec add(number, number) :: number

Он также может использоваться в битовых строках для указания типа заданного битового сегмента:

<<int::integer-little, rest::bits>> = bits

Прочитайте документацию на странице Typespec и <<>>/1 для получения дополнительной информации о типах и битовых строках соответственно.

<<args>> (макрос)

Определяет новую битовую строку.

Примеры

iex> <<1, 2, 3>>
<<1, 2, 3>>

Типы

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

  • integer
  • float
  • bits (псевдоним для bitstring)
  • bitstring
  • binary
  • bytes (псевдоним для binary)
  • utf8
  • utf16
  • utf32

Если тип не указан, по умолчанию используется integer:

iex> <<1, 2, 3>>
<<1, 2, 3>>

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

iex> <<0, "foo">>
<<0, 102, 111, 111>>

Переменные или любые другие типы необходимо явно пометить:

iex> rest = "oo"
iex> <<102, rest>>
** (ArgumentError) argument error

Мы можем решить эту проблему, явно пометив её как binary:

iex> rest = "oo"
iex> <<102, rest::binary>>
"foo"

Типы utf8, utf16, и utf32 предназначены для кодовых точек Юникода. Они также могут применяться к литеральным строкам и символьным последовательностям:

iex> <<"foo"::utf16>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo"::utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>

Параметры

Многие параметры могут быть заданы с использованием - в качестве разделителя. Порядок произвольный, поэтому следующие варианты эквивалентны:

<<102::integer-native, rest::binary>>
<<102::native-integer, rest::binary>>
<<102::unsigned-big-integer, rest::binary>>
<<102::unsigned-big-integer-size(8), rest::binary>>
<<102::unsigned-big-integer-8, rest::binary>>
<<102::8-integer-big-unsigned, rest::binary>>
<<102, rest::binary>>

Единицы и Размер

Длина совпадения равна unit (количество битов) умноженному на size (количество повторяющихся сегментов длиной unit).

Тип Единица по умолчанию
integer 1 бит
float 1 бит
binary 8 бит

Размеры для типов немного сложнее. Размер целых чисел по умолчанию составляет 8.

Для чисел с плавающей точкой он составляет 64. Для чисел с плавающей точкой size * unit должно равняться 32 или 64, соответствуя IEEE 754 двоичной 32 и двоичной 64 соответственно.

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

iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"}

Если не указать размер для не последнего элемента, компиляция завершится ошибкой:

<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
** (CompileError): a binary field without size is only allowed at the end of a binary pattern

Сокращённый Синтаксис

Размер и единицы измерения также могут быть указаны с помощью сокращённого синтаксиса при передаче целочисленных значений:

iex> x = 1
iex> <<x::8>> == <<x::size(8)>>
true
iex> <<x::8*4>> == <<x::size(8)-unit(4)>>
true

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

Модификаторы

Некоторые типы имеют связанные модификаторы для устранения неоднозначности в представлении байтов.

Модификатор Соответствующие типы
signed integer
unsigned (по умолчанию) integer
little integer, float, utf16, utf32
big (по умолчанию) integer, float, utf16, utf32
native integer, utf16, utf32

Знак

Целые числа могут быть signed или unsigned, по умолчанию unsigned.

iex> <<int::integer>> = <<-100>>
<<156>>
iex> int
156
iex> <<int::integer-signed>> = <<-100>>
<<156>>
iex> int
-100

signed и unsigned используются только для соответствия бинарным данным (см. ниже) и только для целых чисел.

iex> <<-100::signed, _rest::binary>> = <<-100, "foo">>
<<156, 102, 111, 111>>

Порядок Байтов

В Elixir три варианта порядка байтов: big, little, и native. По умолчанию big:

iex> <<number::little-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
256
iex> <<number::big-integer-size(16)>> = <<0, 1>>
<<0, 1>>
iex> number
1

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

Сопоставление бинарных/битовых строк

Сопоставление бинарных данных — мощная функция в Elixir, полезная для извлечения информации из бинарных данных, а также для сопоставления шаблонов.

Сопоставление бинарных данных может использоваться само по себе для извлечения информации из бинарных данных:

iex> <<"Hello, ", place::binary>> = "Hello, World"
"Hello, World"
iex> place
"World"

Или как часть определений функций для сопоставления шаблонов:

defmodule ImageTyper
  @png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
                   13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
  @jpg_signature <<255::size(8), 216::size(8)>>

  def type(<<@png_signature, rest::binary>>), do: :png
  def type(<<@jpg_signature, rest::binary>>), do: :jpg
  def type(_), do :unknown
end

Производительность и Оптимизации

Компилятор Erlang может предоставить ряд оптимизаций при создании и сопоставлении бинарных данных. Чтобы увидеть вывод оптимизации, установите параметр компиляции bin_opt_info:

ERL_COMPILER_OPTIONS=bin_opt_info mix compile

Чтобы узнать больше о конкретных оптимизациях и аспектах производительности, ознакомьтесь с Руководством по повышению эффективности Erlang по обработке бинарных данных.

left = right (макрос)

Сопоставляет значение справа с шаблоном слева.

^var (макрос)

Доступ к уже связанной переменной в разделах соответствия. Также известен как оператор фиксации.

Примеры

Elixir позволяет переопределять переменные с помощью статического одиночного присваивания:

iex> x = 1
iex> x = x + 1
iex> x
2

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

iex> x = 1
iex> ^x = List.first([1])
iex> ^x = List.first([2])
** (MatchError) no match of right hand side value: 2

Обратите внимание, что ^x всегда относится к значению x до соответствия. Следующий пример будет совпадать:

iex> x = 0
iex> {x, ^x} = {1, 0}
iex> x
1

__CALLER__ (макрос)

Возвращает текущую среду вызова в виде структуры Macro.Env.

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

__DIR__ (макрос)

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

Хотя к каталогу можно получить доступ как Path.dirname(__ENV__.file), этот макрос является удобным сокращением.

__ENV__ (макрос)

Возвращает текущую информацию об окружении в виде структуры Macro.Env.

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

__MODULE__ (макрос)

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

Хотя к модулю можно получить доступ в __ENV__/0, этот макрос является удобным сокращением.

__aliases__(args) (макрос)

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

Обычно она компилируется в атом:

iex> quote do
...>   Foo.Bar
...> end
{:__aliases__, [alias: false], [:Foo, :Bar]}

Elixir представляет Foo.Bar как __aliases__, поэтому вызовы можно однозначно идентифицировать оператором :.. Например:

iex> quote do
...>   Foo.bar
...> end
{{:., [], [{:__aliases__, [alias: false], [:Foo]}, :bar]}, [], []}

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

С другой стороны, псевдонимы содержат некоторые свойства:

  1. Первый элемент псевдонимов может быть любым термином, который должен быть расширен до атома во время компиляции.

  2. Элементы хвоста псевдонимов гарантированно всегда являются атомами.

  3. Когда первый элемент псевдонимов является атомом :Elixir, расширение не происходит.

__block__(args) (макрос)

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

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

iex> quote do
...>   1
...>   2
...>   3
...> end
{:__block__, [], [1, 2, 3]}

alias(module, opts) (макрос)

alias/2 используется для настройки псевдонимов, что часто бывает полезно с именами модулей.

Примеры

alias/2 может быть использован для настройки псевдонима для любого модуля:

defmodule Math do
  alias MyKeyword, as: Keyword
end

В примере выше, мы настроили MyKeyword для использования псевдонима как Keyword. Теперь любая ссылка на Keyword будет автоматически заменена на MyKeyword.

В случае, если кто-то хочет получить доступ к исходному Keyword, это можно сделать, обратившись к Elixir:

Keyword.values   #=> uses MyKeyword.values
Elixir.Keyword.values #=> uses Keyword.values

Обратите внимание, что вызов alias без опции as: автоматически устанавливает псевдоним, основанный на последней части модуля. Например:

alias Foo.Bar.Baz

Это то же самое, что:

alias Foo.Bar.Baz, as: Baz

Мы также можем установить псевдонимы для нескольких модулей в одной строке:

alias Foo.{Bar, Baz, Biz}

Это то же самое, что:

alias Foo.Bar
alias Foo.Baz
alias Foo.Biz

Смысловая область видимости

import/2, require/2 и alias/2 называются директивами и все имеют смысловую область видимости. Это означает, что вы можете настроить псевдонимы внутри определенных функций, и это не повлияет на общую область видимости.

Предупреждения

Если вы создаете псевдоним для модуля и не используете этот псевдоним, Elixir выведет предупреждение, предполагая, что псевдоним не используется.

В случае, если псевдоним генерируется автоматически макросом, Elixir не выведет предупреждений, так как псевдоним не был явно определен.

Оба поведения предупреждений могут быть изменены путем явного задания опции :warn в значение true или false.

case(condition, clauses) (макрос)

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

Примеры

case thing do
  {:selector, i, value} when is_integer(i) ->
    value
  value ->
    value
end

В примере выше мы сопоставляем thing с каждой частью «заголовка» и выполняем часть «тела», соответствующую первому разделу, который совпадает.

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

x = 10

case x do
  0 ->
    "This clause won't match"
  _ ->
    "This clause would match any value (x = #{x})"
end
#=> "This clause would match any value (x = 10)"

Обработка переменных

Обратите внимание, что переменные, привязанные в части «заголовка», не «утекают» во внешний контекст:

case data do
  {:ok, value} -> value
  :error -> nil
end

value #=> unbound variable value

Однако, переменные, явно привязанные в части «тела», доступны из внешнего контекста:

value = 7

case lucky? do
  false -> value = 13
  true  -> true
end

value #=> 7 or 13

В примере выше, значение будет 7 или 13 в зависимости от значения lucky?. В случае, если у value нет предыдущего значения до case, в разделах, которые не явно привязывают значение, переменная привязана к nil.

Если вы хотите сопоставить шаблон с существующей переменной, вам нужно использовать оператор ^/1:

x = 1

case 10 do
  ^x -> "Won't match"
  _  -> "Will match"
end
#=> "Will match"

cond(clauses) (макрос)

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

cond do
  hd([1, 2, 3]) ->
    "1 is considered as true"
end
#=> "1 is considered as true"

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

Примеры

cond do
  1 + 1 == 1 ->
    "This will never match"
  2 * 2 != 4 ->
    "Nor this"
  true ->
    "This will"
end
#=> "This will"

fn [clauses] end (макрос)

Определяет анонимную функцию.

Примеры

iex> add = fn a, b -> a + b end
iex> add.(1, 2)
3

for(args) (макрос)

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

Начнем с примера:

iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]

Понимание принимает множество генераторов и фильтров. Перечисляемые генераторы определяются с помощью <-:

# A list generator:
iex> for n <- [1, 2, 3, 4], do: n * 2
[2, 4, 6, 8]

# A comprehension with two generators
iex> for x <- [1, 2], y <- [2, 3], do: x * y
[2, 3, 4, 6]

Также могут быть указаны фильтры:

# A comprehension with a generator and a filter
iex> for n <- [1, 2, 3, 4, 5, 6], rem(n, 2) == 0, do: n
[2, 4, 6]

Обратите внимание, что генераторы также могут использоваться для фильтрации, так как они удаляют любое значение, которое не соответствует шаблону слева от <-:

iex> users = [user: "john", admin: "meg", guest: "barbara"]
iex> for {type, name} when type != :guest <- users do
...>   String.upcase(name)
...> end
["JOHN", "MEG"]

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

iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

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

Into

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

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

iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"

Модуль IO предоставляет потоки, которые являются как Enumerable, так и Collectable. Вот эхо-сервер с переводом в верхний регистр, использующий понимания:

for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
  String.upcase(line)
end

import(module, opts) (макрос)

Импортирует функции и макросы из других модулей.

import/2 позволяет легко получить доступ к функциям или макросам из других модулей без использования квалифицированного имени.

Примеры

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

iex> import List
iex> flatten([1, [2], 3])
[1, 2, 3]

Селектор

По умолчанию Elixir импортирует функции и макросы из данного модуля, за исключением тех, которые начинаются с подчёркивания (они обычно являются колбеками):

import List

Разработчик может отфильтровать импорт, чтобы импортировать только макросы или функции, используя опцию only:

import List, only: :functions
import List, only: :macros

В качестве альтернативы, Elixir позволяет разработчику передавать пары имя/арность в :only или :except для точного управления тем, что импортировать (или не импортировать):

import List, only: [flatten: 1]
import String, except: [split: 2]

Обратите внимание, что вызов except для ранее объявленного import/2 просто фильтрует ранее импортированные элементы. Например:

import List, only: [flatten: 1, keyfind: 4]
import List, except: [flatten: 1]

После двух вызовов импорта выше, только List.keyfind/4 будет импортирован.

Функции с подчёркиванием

По умолчанию функции, начинающиеся с _ не импортируются. Если вам действительно нужно импортировать функцию, начинающуюся с _, вы должны явно включить её в селектор :only.

import File.Stream, only: [__build__: 3]

Смысловая область видимости

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

defmodule Math do
  def some_function do
    # 1) Disable "if/2" from Kernel
    import Kernel, except: [if: 2]

    # 2) Require the new "if/2" macro from MyMacros
    import MyMacros

    # 3) Use the new macro
    if do_something, it_works
  end
end

В примере выше мы импортировали макросы из MyMacros, заменив исходную реализацию if/2 нашей собственной внутри этой конкретной функции. Все остальные функции в этом модуле смогут по-прежнему использовать оригинальную.

Предупреждения

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

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

Оба поведения предупреждений могут быть изменены путем явного задания опции :warn в значение true или false.

Неоднозначные имена функций/макросов

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

quote(opts, block) (макрос)

Получает представление любого выражения.

Примеры

iex> quote do
...>   sum(1, 2, 3)
...> end
{:sum, [], [1, 2, 3]}

Описание

Любой код Elixir может быть представлен с помощью структур данных Elixir. Основной блок макросов Elixir — это кортеж из трех элементов, например:

{:sum, [], [1, 2, 3]}

Кортеж выше представляет вызов функции sum, передающей 1, 2 и 3 в качестве аргументов. Элементы кортежа:

  • Первый элемент кортежа всегда атом или другой кортеж с таким же представлением.

  • Второй элемент кортежа представляет метаданные.

  • Третий элемент кортежа — это аргументы вызова функции. Третий аргумент может быть атомом, который обычно является переменной (или локальным вызовом).

Опции

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

  • :location - при установке в :keep, сохраняет текущую строку и файл из quote. Подробная информация о получении информации от Stacktrace представлена ниже.

  • :generated - отмечает данный фрагмент как сгенерированный, чтобы не выводить предупреждения. В настоящее время это работает только со специальными формами (например, вы можете добавить аннотацию к case, но не к if).

  • :context - устанавливает контекст разрешения.

  • :bind_quoted - передаёт привязку макросу. При предоставлении привязки unquote/1 автоматически отключается.

Литералы quote

Помимо кортежа, описанного выше, Elixir имеет несколько литералов, которые при цитировании возвращают себя. Это:

:sum         #=> Atoms
1            #=> Integers
2.0          #=> Floats
[1, 2]       #=> Lists
"strings"    #=> Strings
{key, value} #=> Tuples with two elements

Quote и макросы

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

defmodule Math do
  defmacro squared(x) do
    quote do
      unquote(x) * unquote(x)
    end
  end
end

Мы можем вызвать его так:

import Math
IO.puts "Got #{squared(5)}"

Поначалу в этом примере нет ничего, что бы прямо указывало на то, что это макрос. Но происходит то, что во время компиляции squared(5) превращается в 5 * 5. Аргумент 5 дублируется в сгенерированном коде, но мы можем увидеть это поведение на практике, так как в нашем макросе есть ошибка:

import Math
my_number = fn ->
  IO.puts "Returning 5"
  5
end
IO.puts "Got #{squared(my_number.())}"

Пример выше выведет:

Returning 5
Returning 5
Got 25

Заметьте, как «Возвращает 5» выведено дважды, а не один раз. Это потому, что макрос получает выражение, а не значение (как мы ожидали бы от обычной функции). Это означает, что:

squared(my_number.())

Фактически расширяется до:

my_number.() * my_number.()

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

Давайте исправим наш макрос:

defmodule Math do
  defmacro squared(x) do
    quote do
      x = unquote(x)
      x * x
    end
  end
end

Теперь вызов square(my_number.()) как и раньше, выведет значение только один раз.

На самом деле, эта закономерность настолько распространена, что большинство раз вам понадобится использовать bind_quoted вариант с quote/2:

defmodule Math do
  defmacro squared(x) do
    quote bind_quoted: [x: x] do
      x * x
    end
  end
end

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

Прежде чем закончить это краткое введение, вы заметите, что, хотя мы определили переменную x внутри нашей цитаты:

quote do
  x = unquote(x)
  x * x
end

Когда мы вызываем:

import Math
squared(5)
x #=> ** (CompileError) undefined variable x or undefined function x/0

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

Гигиена в переменных

Рассмотрим следующий пример:

defmodule Hygiene do
  defmacro no_interference do
    quote do
      a = 1
    end
  end
end

require Hygiene

a = 10
Hygiene.no_interference
a #=> 10

В примере выше, a возвращает 10, даже если макрос, по-видимому, устанавливает его в 1, потому что переменные, определенные в макросе, не влияют на контекст, в котором макрос выполняется. Если вы хотите установить или получить переменную в контексте вызывающей стороны, вы можете сделать это с помощью макроса var!:

defmodule NoHygiene do
  defmacro interference do
    quote do
      var!(a) = 1
    end
  end
end

require NoHygiene

a = 10
NoHygiene.interference
a #=> 1

Обратите внимание, что вы не можете получить доступ к переменным, определённым в том же модуле, если явно не укажете контекст:

defmodule Hygiene do
  defmacro write do
    quote do
      a = 1
    end
  end

  defmacro read do
    quote do
      a
    end
  end
end

Hygiene.write
Hygiene.read
#=> ** (RuntimeError) undefined variable a or undefined function a/0

Для этого можно явно передать текущий область видимости модуля в качестве аргумента:

defmodule ContextHygiene do
  defmacro write do
    quote do
      var!(a, ContextHygiene) = 1
    end
  end

  defmacro read do
    quote do
      var!(a, ContextHygiene)
    end
  end
end

ContextHygiene.write
ContextHygiene.read
#=> 1

Гигиена в псевдонимах

Псевдонимы внутри цитаты по умолчанию гигиеничны. Рассмотрим следующий пример:

defmodule Hygiene do
  alias Map, as: M

  defmacro no_interference do
    quote do
      M.new
    end
  end
end

require Hygiene
Hygiene.no_interference #=> %{}

Обратите внимание, что, хотя псевдоним M недоступен в контексте, где макрос расширяется, приведенный выше код работает, потому что M все равно расширяется до Map.

Аналогично, даже если мы определили псевдоним с тем же именем перед вызовом макроса, это не повлияет на результат макроса:

defmodule Hygiene do
  alias Map, as: M

  defmacro no_interference do
    quote do
      M.new
    end
  end
end

require Hygiene
alias SomethingElse, as: M
Hygiene.no_interference #=> %{}

В некоторых случаях вам нужно получить доступ к псевдониму или модулю, определённому в вызывающей стороне. Для этого можно использовать макрос alias!:

defmodule Hygiene do
  # This will expand to Elixir.Nested.hello
  defmacro no_interference do
    quote do
      Nested.hello
    end
  end

  # This will expand to Nested.hello for
  # whatever is Nested in the caller
  defmacro interference do
    quote do
      alias!(Nested).hello
    end
  end
end

defmodule Parent do
  defmodule Nested do
    def hello, do: "world"
  end

  require Hygiene
  Hygiene.no_interference
  #=> ** (UndefinedFunctionError) ...

  Hygiene.interference
  #=> "world"
end

Гигиена в импортах

Аналогично псевдонимам, импорты в Elixir гигиеничны. Рассмотрим следующий код:

defmodule Hygiene do
  defmacrop get_length do
    quote do
      length([1, 2, 3])
    end
  end

  def return_length do
    import Kernel, except: [length: 1]
    get_length
  end
end

Hygiene.return_length #=> 3

Обратите внимание, как Hygiene.return_length/0 возвращает 3, хотя функция Kernel.length/1 не импортирована. Фактически, даже если return_length/0 импортировал функцию с тем же именем и арностью из другого модуля, это не повлияет на результат функции:

def return_length do
  import String, only: [length: 1]
  get_length
end

Вызов этого нового return_length/0 по-прежнему вернёт 3 в качестве результата.

Elixir достаточно умён, чтобы отложить разрешение до последнего возможного момента. Таким образом, если вы вызываете length([1, 2, 3]) внутри цитаты, но функция length/1 недоступна, она расширяется в вызывающей стороне:

defmodule Lazy do
  defmacrop get_length do
    import Kernel, except: [length: 1]

    quote do
      length("hello")
    end
  end

  def return_length do
    import Kernel, except: [length: 1]
    import String, only: [length: 1]
    get_length
  end
end

Lazy.return_length #=> 5

Информация о стеке вызовов

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

# adder.ex
defmodule Adder do
  @doc "Defines a function that adds two numbers"
  defmacro defadd do
    quote location: :keep do
      def add(a, b), do: a + b
    end
  end
end

# sample.ex
defmodule Sample do
  import Adder
  defadd
end

require Sample
Sample.add(:one, :two)
#=> ** (ArithmeticError) bad argument in arithmetic expression
#=>     adder.ex:5: Sample.add/2

При использовании location: :keep и передаче некорректных аргументов в Sample.add/2, информация о стеке вызовов будет указывать на файл и строку внутри цитаты. Без location: :keep, ошибка сообщается туда, где defadd был вызван. Обратите внимание, что location: :keep влияет только на определения внутри цитаты.

Связывание и фрагменты unquote

Механизмы Elixir quote/unquote предоставляют функциональность, называемую фрагментами unquote. Фрагменты unquote обеспечивают простой способ генерировать функции на лету. Рассмотрим этот пример:

kv = [foo: 1, bar: 2]
Enum.each kv, fn {k, v} ->
  def unquote(k)(), do: unquote(v)
end

В примере выше мы динамически сгенерировали функции foo/0 и bar/0. Теперь представьте, что мы хотим преобразовать эту функциональность в макрос:

defmacro defkv(kv) do
  Enum.map kv, fn {k, v} ->
    quote do
      def unquote(k)(), do: unquote(v)
    end
  end
end

Мы можем вызвать этот макрос так:

defkv [foo: 1, bar: 2]

Однако мы не можем вызвать его так:

kv = [foo: 1, bar: 2]
defkv kv

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

Это распространённая ошибка при разработке макросов. Мы предполагаем определённую форму в макросе. Мы можем обойти это, unquoting переменную внутри выражения цитаты:

defmacro defkv(kv) do
  quote do
    Enum.each unquote(kv), fn {k, v} ->
      def unquote(k)(), do: unquote(v)
    end
  end
end

Если вы попытаетесь запустить наш новый макрос, вы заметите, что он даже не будет компилироваться, жалуясь, что переменные k и v не существуют. Это связано с неоднозначностью: unquote(k) может быть фрагментом unquote, как раньше, или обычным unquote, как в unquote(kv).

Одно из решений этой проблемы – отключить unquoting в макросе, однако это сделает невозможным внедрение представления kv в дерево. Вот где опция :bind_quoted (снова!) приходит на помощь. Используя :bind_quoted, мы можем автоматически отключить unquoting, одновременно внедряя необходимые переменные в дерево:

defmacro defkv(kv) do
  quote bind_quoted: [kv: kv] do
    Enum.each kv, fn {k, v} ->
      def unquote(k)(), do: unquote(v)
    end
  end
end

На самом деле, опция :bind_quoted рекомендуется всякий раз, когда требуется ввести значение в цитату.

receive(args) (макрос)

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

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

Примеры

receive do
  {:selector, i, value} when is_integer(i) ->
    value
  value when is_atom(value) ->
    value
  _ ->
    IO.puts :stderr, "Unexpected message received"
end

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

receive do
  {:selector, i, value} when is_integer(i) ->
    value
  value when is_atom(value) ->
    value
  _ ->
    IO.puts :stderr, "Unexpected message received"
after
  5000 ->
    IO.puts :stderr, "No message in 5 seconds"
end

Пункт after может быть указан, даже если нет пунктов сопоставления. Значение тайм-аута, переданное в after может быть любым выражением, оценивающимся как одно из разрешённых значений:

  • :infinity — процесс должен ждать неопределённое время соответствия сообщения; это то же самое, что и не использовать тайм-аут

  • 0 — если в почтовом ящике нет соответствующего сообщения, тайм-аут наступит немедленно

  • положительное целое число меньше 4_294_967_295 (0xFFFFFFFF в шестнадцатеричном формате) — оно должно быть представимо как беззнаковое целое 32-битное число.

Обработка переменных

Специальная форма receive/1 обрабатывает переменные точно так же, как и специальный макрос case/2. Для получения дополнительной информации см. документацию по case/2.

require(module, opts) (макрос)

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

Примеры

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

Предположим, вы создали свою собственную реализацию if/2 в модуле MyMacros. Если вы хотите вызвать её, вам необходимо предварительно явно потребовать MyMacros:

defmodule Math do
  require MyMacros
  MyMacros.if do_something, it_works
end

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

Свертка псевдонима

require/2 также принимает as: в качестве параметра, поэтому он автоматически создаёт псевдоним. Для получения дополнительной информации см. alias/2.

super(args) (макрос)

Вызывает переопределённую функцию при её переопределении с помощью Kernel.defoverridable/1.

См. Kernel.defoverridable/1 для получения дополнительной информации и документации.

try(args) (макрос)

Вычисляет заданные выражения и обрабатывает любые ошибки, завершения или бросания, которые могли произойти.

Примеры

try do
  do_something_that_may_fail(some_arg)
rescue
  ArgumentError ->
    IO.puts "Invalid argument given"
catch
  value ->
    IO.puts "Caught #{inspect(value)}"
else
  value ->
    IO.puts "Success! The result was #{inspect(value)}"
after
  IO.puts "This is printed regardless if it failed or succeed"
end

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

Обратите внимание, что вызовы внутри try/1 не являются рекурсивными по хвосту, так как виртуальной машине нужно сохранить стековую трассировку на случай возникновения исключения.

rescue пункты

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

try do
  UndefinedModule.undefined_function
rescue
  UndefinedFunctionError -> nil
end

try do
  UndefinedModule.undefined_function
rescue
  [UndefinedFunctionError] -> nil
end

# rescue and bind to x
try do
  UndefinedModule.undefined_function
rescue
  x in [UndefinedFunctionError] -> nil
end

# rescue all and bind to x
try do
  UndefinedModule.undefined_function
rescue
  x -> nil
end

Ошибки Erlang

Ошибки Erlang преобразуются в ошибки Elixir при обработке:

try do
  :erlang.error(:badarg)
rescue
  ArgumentError -> :ok
end

Наиболее распространённые ошибки Erlang будут преобразованы в их аналоги в Elixir. Те, которые не будут преобразованы, будут преобразованы в ErlangError:

try do
  :erlang.error(:unknown)
rescue
  ErlangError -> :ok
end

Фактически, ErlangError может быть использован для обработки любой ошибки, которая не является правильной ошибкой Elixir. Например, он может быть использован для обработки предыдущей :badarg ошибки тоже, до преобразования:

try do
  :erlang.error(:badarg)
rescue
  ErlangError -> :ok
end

Перехват бросков и выходов

Оператор catch может быть использован для перехвата брошенных значений и выходов.

try do
  exit(:shutdown)
catch
  :exit, :shutdown ->
    IO.puts "Exited with shutdown reason"
end

try do
  throw(:sample)
catch
  :throw, :sample ->
    IO.puts ":sample was thrown"
end

Оператор catch также поддерживает :error наряду с :exit и :throw, как и в Erlang, хотя он обычно избегается в пользу механизмов управления raise/rescue. Одна из причин этого заключается в том, что при перехвате :error, ошибка не преобразуется автоматически в ошибку Elixir:

try do
  :erlang.error(:badarg)
catch
  :error, :badarg ->
    :ok
end

Обратите внимание, что можно сопоставить как перехваченное значение, так и тип этого значения:

try do
  exit(:shutdown)
catch
  kind, value when kind in [:exit, :throw] ->
    IO.puts "Exited with or thrown value #{inspect(value)}"
end

Операторы after

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

File.write!("tmp/story.txt", "Hello, World")
try do
  do_something_with("tmp/story.txt")
after
  File.rm("tmp/story.txt")
end

Операторы else

Операторы else позволяют сопоставить результат выражения с шаблоном:

x = 2
try do
  1 / x
rescue
  ArithmeticError ->
    :infinity
else
  y when y < 1 and y > -1 ->
    :small
  _ ->
    :large
end

Если оператор else отсутствует и исключения не возникают, возвращается результат выражения:

x = 1
^x =
  try do
    1 / x
  rescue
    ArithmeticError ->
      :infinity
  end

Однако, если оператор else присутствует, но результат выражения не соответствует ни одному из шаблонов, будет поднято исключение. Это исключение не будет перехвачено операторами catch или rescue в том же try:

x = 1
try do
  try do
    1 / x
  rescue
    # The TryClauseError can not be rescued here:
    TryClauseError ->
      :error_a
  else
    0 ->
      :small
  end
rescue
  # The TryClauseError is rescued here:
  TryClauseError ->
    :error_b
end

Аналогично, исключение внутри оператора else не перехватывается или обрабатывается внутри того же оператора try:

try do
  try do
    nil
  catch
    # The exit(1) call below can not be caught here:
    :exit, _ ->
      :exit_a
  else
    _ ->
      exit(1)
  end
catch
  # The exit is caught here:
  :exit, _ ->
    :exit_b
end

Это означает, что виртуальная машина больше не нуждается в сохранении стека отслеживания после попадания в оператор else и поэтому возможна хвостовая рекурсия при использовании оператора try с вызовом хвоста в качестве последнего вызова внутри оператора else. То же самое верно для операторов rescue и catch.

Только результат выражения переходит к оператору else. Если оператор try попадает в операторы rescue или catch, их результат не переходит к оператору else:

try do
  throw(:catch_this)
catch
  :throw, :catch_this ->
    :it_was_caught
else
  # :it_was_caught will not fall down to this "else" clause.
  other ->
    {:else, other}
end

Обработка переменных

Поскольку выражение внутри оператора try может не быть вычислено из-за исключения, любая переменная, созданная внутри оператора try, не может быть обращена к ней извне. Например:

try do
  x = 1
  do_something_that_may_fail(same_arg)
  :ok
catch
  _, _ -> :failed
end

x #=> unbound variable "x"

В примере выше, переменная x недоступна, так как она была определена внутри оператора try. Распространённой практикой для решения этой проблемы является возврат переменных, определённых внутри оператора try:

x =
  try do
    x = 1
    do_something_that_may_fail(same_arg)
    x
  catch
    _, _ -> :failed
  end

unquote(expr) (макрос)

Раскручивает данное выражение изнутри макроса.

Примеры

Представьте ситуацию, когда у вас есть переменная value и вы хотите вставить её внутри некоторой цитаты. Первая попытка была бы:

value = 13
quote do
  sum(1, value, 3)
end

Что затем вернёт:

{:sum, [], [1, {:value, [], quoted}, 3]}

Что не является ожидаемым результатом. Для этого мы используем unquote:

iex> value = 13
iex> quote do
...>   sum(1, unquote(value), 3)
...> end
{:sum, [], [1, 13, 3]}

unquote_splicing(expr) (макрос)

Раскручивает данный список, расширяя его аргументы. Аналогично unquote/1.

Примеры

iex> values = [2, 3, 4]
iex> quote do
...>   sum(1, unquote_splicing(values), 5)
...> end
{:sum, [], [1, 2, 3, 4, 5]}

with(args) (макрос)

Используется для объединения сопоставляющих операторов.

Начнём с примера:

iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...>      {:ok, height} <- Map.fetch(opts, :height),
...>      do: {:ok, width * height}
{:ok, 150}

Если все операторы сопоставляются, блок do выполняется, возвращая его результат. В противном случае цепочка прерывается, и возвращается несопоставленное значение:

iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...>      {:ok, height} <- Map.fetch(opts, :height),
...>      do: {:ok, width * height}
:error

Ограничители могут использоваться в шаблонах также:

iex> users = %{"melany" => "guest", "bob" => :admin}
iex> with {:ok, role} when not is_binary(role) <- Map.fetch(users, "bob"),
...>      do: {:ok, to_string(role)}
{:ok, "admin"}

Как и в for/1, переменные, связанные внутри with/1, не будут «вытекать»; также могут быть вставлены «голые выражения» между операторами:

iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...>      double_width = width * 2,
...>      {:ok, height} <- Map.fetch(opts, :height),
...>      do: {:ok, double_width * height}
{:ok, 300}
iex> width
nil

Опция else может быть предоставлена для изменения того, что возвращается от with в случае неудачного сопоставления:

iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...>      {:ok, height} <- Map.fetch(opts, :height) do
...>   {:ok, width * height}
...> else
...>   :error ->
...>     {:error, :wrong_data}
...> end
{:error, :wrong_data}

Если нет подходящего условия else, то возникает исключение WithClauseError.

{args} (макрос)

Создаёт кортеж.

Дополнительную информацию о типе данных кортежа и о функциях для работы с кортежами можно найти в модуле Tuple; некоторые функции для работы с кортежами также доступны в модуле Kernel (такие как Kernel.elem/2 или Kernel.tuple_size/1).

Представление AST

Только кортежи из двух элементов считаются литералами в Elixir и возвращают сами себя при цитировании. Следовательно, все остальные кортежи представляются в AST как вызовы специальной формы :{}.

iex> quote do
...>   {1, 2}
...> end
{1, 2}

iex> quote do
...>   {1, 2, 3}
...> end
{:{}, [], [1, 2, 3]}

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.5.3/Kernel.SpecialForms.html

Spec-Zone.ru

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