Kernel.SpecialForms
Специальные формы являются основными строительными блоками Elixir и поэтому не могут быть переопределены разработчиком.
Мы определяем их в этом модуле. Некоторые из этих форм являются лексическими (например, alias/2, case/2 и т. д.). Макросы {} и <<>> также являются специальными формами, используемыми для определения кортежей и бинарных структур данных соответственно.
В этом модуле также документированы псевдопеременные Elixir (__ENV__, __MODULE__, __DIR__ и __CALLER__). Псевдопеременные возвращают информацию об окружении компиляции Elixir и могут только считываться, но никогда не присваиваться.
Наконец, он также документирует две специальные формы, __block__ и __aliases__, которые не предназначены для прямого вызова разработчиком, но они появляются в цитируемом содержании, поскольку они необходимы в конструкциях 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} Структура также проверяет, являются ли заданные ключи частью определённой структуры. Приведенный ниже пример завершится ошибкой, поскольку в структуре User нет ключа :full_name:
%User{full_name: "john doe"} Также доступна операция обновления, специфичная для структур:
%User{user | age: 28} Вышеприведённый синтаксис гарантирует, что заданные ключи будут валидны на стадии компиляции, и он гарантирует, что в ходе выполнения заданный аргумент является структурой, в противном случае произойдёт ошибка BadStructError.
Хотя структуры являются картами, по умолчанию структуры не реализуют ни одного из протоколов, реализованных для карт. См. Kernel.defprotocol/2 для получения дополнительной информации о том, как структуры могут использоваться с протоколами для полиморфной диспетчеризации. Также см. Kernel.struct/2 и Kernel.struct!/2 для примеров создания и обновления структур динамически.
%{} (макрос)
Создаёт карту.
Карты — это хранилища ключ-значение, где ключи сравниваются с помощью оператора сопоставления (===). Карты могут быть созданы с помощью специальной формы %{}, где ключи связаны через =>:
%{1 => 2} Карты также поддерживают ключевой синтаксис, как и другие специальные формы, если они находятся в конце списка аргументов:
%{hello: :world, with: :keywords}
%{:hello => :world, with: :keywords} Если карта имеет дублирующиеся ключи, последний ключ всегда будет иметь более высокий приоритет:
iex> %{a: :b, a: :c}
%{a: :c} Удобные функции для работы с картами можно найти в модуле Map.
Синтаксис доступа
Помимо функций доступа, доступных в модуле Map, таких как Map.get/3 и Map.fetch/2, к карте можно получить доступ, используя оператор .:
iex> map = %{a: :b}
iex> map.a
:b Обратите внимание, что оператор . ожидает, что ключ :a существует в карте. Если нет, генерируется ошибка ArgumentError.
Синтаксис обновления
Карты также поддерживают синтаксис обновления:
iex> map = %{:a => :b}
iex> %{map | :a => :c}
%{:a => :c} Обратите внимание, что синтаксис обновления требует, чтобы заданные ключи существовали. Попытка обновить ключ, которого нет, вызовет ошибку KeyError.
Представление 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."HELLO" будет обрабатываться как удалённый вызов, а не как псевдоним. Такое решение было принято, чтобы каждый раз, когда используются одинарные или двойные кавычки, у нас был удалённый вызов, независимо от содержимого кавычек. Это решение также отражено в обсуждаемых ниже выражениях в кавычках.
Выражение в кавычках
Когда используется ., выражение в кавычках может принимать две формы. Когда правая часть начинается со строчной буквы (или символа подчеркивания):
iex> quote do
...> String.downcase("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]} Обратите внимание, что у нас есть внутренний кортеж, содержащий атом :., представляющий точку в качестве первого элемента:
{:., [], [{:__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__.
Раскуорирование
Мы также можем использовать unquote для генерации удалённого вызова в выражении в кавычках:
iex> x = :downcase
iex> quote do
...> String.unquote(x)("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]} Подобно Kernel."HELLO", 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 -
binary -
bytes(псевдоним для бинарного) -
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
Мы можем решить эту проблему, явно пометив её как бинарный:
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 binary32 и binary64 соответственно.
Для бинарных данных по умолчанию является размер бинарных данных. Только последний бинарный объект в совпадении может использовать размер по умолчанию. Все остальные должны иметь свой размер, указанный явно, даже если совпадение однозначное. Например:
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, utf16, utf32
|
big (по умолчанию) |
integer, 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__, этот макрос является удобным сокращением.
__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]}, [], []} Когда итератор выражения видит :. в качестве ключа кортежа, он может быть уверен, что это представляет вызов, а второй аргумент в списке — атом.
С другой стороны, псевдонимы содержат некоторые свойства:
-
Первый элемент псевдонимов может быть любым термином, который должен быть преобразован в атом во время компиляции.
-
Элементы хвоста псевдонимов гарантированно всегда будут атомами.
-
Когда первый элемент псевдонимов — атом
: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
Лексическая область видимости
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
Разработчик может фильтровать, импортируя только макросы или функции с помощью опции 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, отключает развёртывание. Полезно, когда у вас есть кавычка внутри другой кавычки и вы хотите управлять тем, какие кавычки можно разворачивать. -
: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 Обратите внимание, как return_length возвращает 3, даже если функция length/1 не импортирована. На самом деле, даже если return_length импортировала функцию с тем же именем и арностью из другого модуля, это не повлияет на результат функции:
def return_length do import String, only: [length: 1] get_length end
Вызов этого нового return_length по-прежнему вернёт 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) (macro)
Оценивает заданные выражения и обрабатывает любые ошибки, завершения или броски, которые могли произойти.
Примеры
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) (macro)
Раскусывает данное выражение изнутри макроса.
Примеры
Представьте ситуацию, когда у вас есть переменная 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) (macro)
Раскусывает данный список, расширяя его аргументы. Аналогично 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) (macro)
Используется для объединения блоков сопоставления.
Начнём с примера:
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} (macro)
Создаёт кортеж.
Только кортежи из двух элементов считаются литералами в Elixir. Следовательно, все остальные кортежи представлены в AST как вызов специальной формы :{}.
Удобства для работы с кортежами можно найти в модуле Tuple. Некоторые функции для работы с кортежами также доступны в Kernel, а именно Kernel.elem/2, Kernel.put_elem/3 и Kernel.tuple_size/1.
Примеры
iex> {1, 2, 3}
{1, 2, 3}
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.3.4/Kernel.SpecialForms.html