Spec-Zone.ru › Elixir 1.4

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> 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> 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
...>   Hello.World
...> end
{:__aliases__, [alias: false], [:Hello, :World]}

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

Раскрытие цитирования

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

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 предназначены для кодовых точек Unicode. Они также могут применяться к литеральным строкам и спискам символов:

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 , которая принимает любую структуру, если она реализует протокол 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

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

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, отключает разыменование. Полезно, когда у вас есть кавычка внутри другой кавычки, и вы хотите контролировать, какие кавычки могут быть разыменованы.

  • :location — когда установлено в :keep, сохраняет текущую строку и файл из кавычки. Подробности см. в разделе «Информация о стеке вызовов» ниже.

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

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

  • :bind_quoted — передает связывание макросу. Всякий раз, когда задаётся связывание, unquote/1 автоматически отключается.

Литералы кавычек

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

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

Цитирование и макросы

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 может использоваться во многих случаях и считается хорошей практикой, не только потому, что помогает избежать распространённых ошибок, но и потому, что позволяет использовать другие инструменты, предоставляемые макросами, такие как фрагменты разыменования, обсуждаемые в некоторых разделах ниже.

Прежде чем закончить это краткое введение, вы заметите, что, хотя мы определили переменную 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 влияет только на определения внутри цитирования.

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

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

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, наш код завершается с ошибкой.

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

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

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

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 — если в почтовом ящике нет совпадающего сообщения, таймаут произойдёт немедленно

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

Специальная форма 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 #{value}"
else
  value ->
    IO.puts "Success! The result was #{value}"
after
  IO.puts "This is printed regardless if it failed or succeed"
end

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

Обратите внимание, что вызовы внутри 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 во время rescue:

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 thrown"
end

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

Блоки after

Блок after позволяет определить логику очистки, которая будет вызвана как при успешном выполнении блока try, так и при возникновении ошибки. Обратите внимание, что процесс завершит работу как обычно при получении сигнала выхода, который приводит к его преждевременному завершению, и поэтому выполнение блока 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, и поэтому возможна рекурсия по хвосту при использовании блока else с вызовом по хвосту в качестве последнего вызова внутри блока else. То же самое верно для блоков try и rescue и catch.

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

Поскольку выражение внутри блока 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.4.5/Kernel.SpecialForms.html

Spec-Zone.ru

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