Kernel.SpecialForms
Специальные формы являются основными строительными блоками Elixir и поэтому не могут быть переопределены разработчиком.
Мы определяем их в этом модуле. Некоторые из этих форм являются лексическими (например, alias/2, case/2 и т. д.). Макросы {}/1 и <<>>/1 также являются специальными формами, используемыми для определения кортежей и бинарных структур данных соответственно.
Этот модуль также документирует макросы, которые возвращают информацию об окружении компиляции Elixir, например (__ENV__/0, __MODULE__/0, __DIR__/0 и __CALLER__/0).
Наконец, он также документирует две специальные формы, __block__/1 и __aliases__/1, которые не предназначены для прямого вызова разработчиком, но они появляются в цитируемом содержимом, поскольку они необходимы в конструкциях Elixir.
Краткое описание
Функции
- %struct{}
Сопоставление со структурой или создание структуры.
- %{}
Создаёт карту.
- &(expr)
Захватывает или создаёт анонимную функцию.
- left . right
Определяет удалённый вызов, вызов анонимной функции или псевдоним.
- left :: right
Используется типами и битовыми строками для указания типов.
- <<args>>
Определяет новую битовую строку.
- left = right
Сопоставляет значение справа с шаблоном слева.
- ^var
Доступ к уже связанной переменной в пунктах сопоставления. Также известен как оператор привязки.
- __CALLER__
Возвращает текущую среду вызова как структуру
Macro.Env.- __DIR__
Возвращает абсолютный путь к каталогу текущего файла в виде бинарного объекта.
- __ENV__
Возвращает текущую информацию об окружающей среде как структуру
Macro.Env.- __MODULE__
Возвращает имя текущего модуля как атом или
nilв противном случае.- __STACKTRACE__
Возвращает стек вызовов для текущего обрабатываемого исключения.
- __aliases__(args)
Внутренняя специальная форма для хранения информации о псевдонимах.
- __block__(args)
Внутренняя специальная форма для выражений блоков.
- alias(module, opts)
alias/2используется для настройки псевдонимов, что часто бывает полезно с именами модулей.- case(condition, clauses)
Сопоставляет данное выражение с заданными пунктами.
- cond(clauses)
Вычисляет выражение, соответствующее первому пункту, который вычисляется как истинное значение.
- fn
Определяет анонимную функцию.
- 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)
Разцитирует данный список, расширяя его аргументы.
- with(args)
Используется для объединения пунктов сопоставления.
- {args}
Создаёт кортеж.
Функции
%struct{}
(макрос)Сопоставление со структурой или создание структуры.
Структура — это помеченная карта, которая позволяет разработчикам предоставлять значения по умолчанию для ключей, теги для использования в полиморфных диспетчерах и утверждениях на этапе компиляции.
Структуры обычно определяются с помощью макроса Kernel.defstruct/1:
defmodule User do defstruct name: "john", age: 27 end
Теперь структуру можно создать следующим образом:
%User{} Внутри структура — это просто карта с ключом :__struct__, указывающим на модуль User:
%User{} == %{__struct__: User, name: "john", age: 27} Поля структуры можно указать при создании структуры:
%User{age: 31}
#=> %{__struct__: User, name: "john", age: 31} Или также при сопоставлении шаблонов для извлечения значений:
%User{age: age} = user Также доступна операция обновления, специфичная для структур:
%User{user | age: 28} Преимущество структур заключается в том, что они проверяют, являются ли заданные ключи частью определённой структуры. Пример ниже завершится ошибкой, так как ключа :full_name нет в структуре User:
%User{full_name: "john doe"} Вышеприведённый синтаксис гарантирует, что заданные ключи являются допустимыми на этапе компиляции, и гарантирует на этапе выполнения, что заданный аргумент является структурой, в противном случае выводится ошибка BadStructError.
Хотя структуры являются картами, по умолчанию структуры не реализуют ни один из протоколов, реализованных для карт. Подробнее см. Kernel.defprotocol/2 для получения дополнительной информации о том, как структуры могут использоваться с протоколами для полиморфной отправки. Также см. Kernel.struct/2 и Kernel.struct!/2 для примеров создания и обновления структур динамически.
Сопоставление шаблонов с именами структур
Помимо возможности сопоставления шаблонов с полями структур, такими как:
%User{age: age} = user Структуры также позволяют сопоставление шаблонов с именем структуры:
%struct_name{} = user
struct_name #=> User Также можно присвоить имя структуры _ , когда нужно проверить, является ли что-то структурой, но вас не интересует её имя:
%_{} = user %{}
(макрос)Создаёт карту.
Дополнительную информацию о картах, их синтаксисе и способах доступа и обработки см. в модуле 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> take_five = &Enum.take(&1, 5) iex> take_five.(1..10) [1, 2, 3, 4, 5]
Ещё один пример с использованием импортированной или локальной функции:
iex> first_elem = &elem(&1, 0)
iex> first_elem.({0, 1})
0 Оператор & может использоваться с более сложными выражениями:
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, 3])
[1, 2, 3] Единственные ограничения при создании анонимных функций заключаются в том, что должно быть присутствовать как минимум одна заглушка, т. е. она должна содержать как минимум &1, и что выражения блоков не поддерживаются:
# No placeholder, fails to compile. &(:foo) # Block expression, fails to compile. &(&1; &2)
left . right
(макрос)Определяет удалённый вызов, вызов анонимной функции или псевдоним.
Точка (.) в Elixir может использоваться для удалённых вызовов:
iex> String.downcase("FOO")
"foo" В этом примере мы использовали . для вызова downcase в модуле String, передавая "FOO" в качестве аргумента.
Точка также может использоваться для вызова анонимных функций:
iex> (fn n -> n end).(7) 7
в этом случае слева есть функция.
Мы также можем использовать точку для создания псевдонимов:
iex> Hello.World Hello.World
На этот раз мы объединили два псевдонима, определив конечный псевдоним Hello.World.
Синтаксис
Правая часть . может быть словом, начинающимся с большой буквы, которое представляет псевдоним, словом, начинающимся со строчной буквы или подчеркивания, любым допустимым оператором языка или любым именем, заключённым в одинарные или двойные кавычки. Все это допустимые примеры:
iex> Kernel.Sample Kernel.Sample iex> Kernel.length([1, 2, 3]) 3 iex> Kernel.+(1, 2) 3 iex> Kernel."+"(1, 2) 3
Заключение имени функции в одинарные или двойные кавычки всегда является удалённым вызовом. Следовательно, Kernel."Foo" попытается вызвать функцию «Foo» и не вернёт псевдоним Kernel.Foo. Это сделано по умолчанию, так как имена модулей более жёсткие, чем имена функций.
Когда для вызова анонимной функции используется точка, существует только один операнд, но он всё равно записывается с помощью постфиксной нотации:
iex> negate = fn n -> -n end iex> negate.(7) -7
Цитата выражения
Когда . используется, цитируемое выражение может принимать две различные формы. Когда правая часть начинается со строчной буквы (или подчёркивания):
iex> quote do
...> String.downcase("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]} Обратите внимание, что у нас есть внутреннее кортеж, содержащий атом :. , представляющий точку в качестве первого элемента:
{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]} Этот кортеж следует общей структуре цитируемого выражения в Elixir, с именем в качестве первого аргумента, списком ключевых слов в качестве метаданных как второго и списком аргументов как третьего. В этом случае аргументы — это псевдоним String и атом :downcase. Второй аргумент в удалённом вызове — всегда атом.
В случае вызовов анонимных функций внутренний кортеж со специальной формой точки имеет только один аргумент, отражая тот факт, что оператор является унарным:
iex> quote do
...> negate.(0)
...> end
{{:., [], [{:negate, [], __MODULE__}]}, [], [0]} Когда правая часть является псевдонимом (т.е. начинается с большой буквы), мы получаем вместо этого:
iex> quote do
...> Hello.World
...> end
{:__aliases__, [alias: false], [:Hello, :World]} Мы более подробно рассмотрим псевдонимы в документации по специальной форме __aliases__/1.
Развенчивание
Мы также можем использовать unquote для генерации удалённого вызова в цитируемом выражении:
iex> x = :downcase
iex> quote do
...> String.unquote(x)("FOO")
...> end
{{:., [], [{:__aliases__, [alias: false], [:String]}, :downcase]}, [], ["FOO"]} Аналогично Kernel."FUNCTION_NAME", unquote(x) всегда будет генерировать удалённый вызов, независимо от значения x. Чтобы сгенерировать псевдоним через цитируемое выражение, необходимо полагаться на Module.concat/2:
iex> x = Sample
iex> quote do
...> Module.concat(String, unquote(x))
...> end
{{:., [], [{:__aliases__, [alias: false], [:Module]}, :concat]}, [],
[{:__aliases__, [alias: false], [:String]}, Sample]} left :: right
(макрос)Используется типами и битовыми строками для указания типов.
Этот оператор используется в двух различных случаях в Elixir. Он используется в типах для указания типа переменной, функции или самого типа:
@type number :: integer | float @spec add(number, number) :: number
Он также может использоваться в битовых строках для указания типа заданного битового сегмента:
<<int::integer-little, rest::bits>> = bits
Прочитайте документацию на странице Typespec и <<>>/1 для получения дополнительной информации о типах и битовых строках соответственно.
<<args>>
(макрос)Определяет новую битовую строку.
Примеры
iex> <<1, 2, 3>> <<1, 2, 3>>
Типы
Битовая строка состоит из многих сегментов, и каждый сегмент имеет тип. В битовых строках используются 9 типов:
-
integer -
float -
bits(псевдоним дляbitstring) -
bitstring -
binary -
bytes(псевдоним дляbinary) -
utf8 -
utf16 -
utf32
Если тип не указан, по умолчанию используется integer:
iex> <<1, 2, 3>> <<1, 2, 3>>
Elixir также по умолчанию принимает сегмент в виде литеральной строки или литеральной списков символов, которые по умолчанию расширяются до целых чисел:
iex> <<0, "foo">> <<0, 102, 111, 111>>
Переменные или любой другой тип необходимо явно пометить:
iex> rest = "oo" iex> <<102, rest>> ** (ArgumentError) argument error
Мы можем решить эту проблему, явно пометив её как binary:
iex> rest = "oo" iex> <<102, rest::binary>> "foo"
Типы utf8, utf16, и utf32 предназначены для кодовых точек Юникода. Они также могут применяться к литеральным строкам и спискам символов:
iex> <<"foo"::utf16>> <<0, 102, 0, 111, 0, 111>> iex> <<"foo"::utf32>> <<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
Параметры
Многие параметры могут быть заданы с использованием - в качестве разделителя. Порядок произволен, поэтому следующие варианты равнозначны:
<<102::integer-native, rest::binary>> <<102::native-integer, rest::binary>> <<102::unsigned-big-integer, rest::binary>> <<102::unsigned-big-integer-size(8), rest::binary>> <<102::unsigned-big-integer-8, rest::binary>> <<102::8-integer-big-unsigned, rest::binary>> <<102, rest::binary>>
Единица и размер
Длина совпадения равна unit (количество бит) умноженному на size (количество повторяющихся сегментов длиной unit).
| Тип | Единица по умолчанию |
|---|---|
integer |
1 бит |
float |
1 бит |
binary |
8 бит |
Размеры для типов немного сложнее. Размер по умолчанию для целых чисел — 8.
Для чисел с плавающей точкой — 64. Для чисел с плавающей точкой, size * unit должно быть 32 или 64, что соответствует IEEE 754 binary32 и binary64 соответственно.
Для бинарных данных значение по умолчанию — размер бинарных данных. Только последняя бинарная переменная в совпадении может использовать размер по умолчанию. Все остальные должны иметь свой размер, указанный явно, даже если совпадение однозначно. Например:
iex> <<name::binary-size(5), " the ", species::binary>> = <<"Frank the Walrus">>
"Frank the Walrus"
iex> {name, species}
{"Frank", "Walrus"} Размер может быть переменной:
iex> name_size = 5
iex> <<name::binary-size(name_size), " the ", species::binary>> = <<"Frank the Walrus">>
iex> {name, species}
{"Frank", "Walrus"} И переменная может быть определена в самом совпадении (до её использования):
iex> <<name_size::size(8), name::binary-size(name_size), " the ", species::binary>> = <<5, "Frank the Walrus">>
iex> {name, species}
{"Frank", "Walrus"} Однако, размер не может быть определён в совпадении вне соответствия с бинарными/битовыми строками:
{name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, <<"Frank the Walrus">>}
** (CompileError): undefined variable "name_size" in bitstring segment Отсутствие указания размера для не последнего элемента приводит к ошибке компиляции:
<<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 do
@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 для работы с бинарными данными на сайте 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, этот макрос является удобным сокращением.
__STACKTRACE__
(макрос)Возвращает стек вызовов для текущей обрабатываемой исключительной ситуации.
Доступно только в catch и rescue частях выражений try/1.
Для получения стека вызовов текущего процесса используйте Process.info(self(), :current_stacktrace).
__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
Мы также можем создать псевдонимы для нескольких модулей в одной строке:
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
(макрос)Определяет анонимную функцию.
Примеры
iex> add = fn a, b -> a + b end iex> add.(1, 2) 3
Анонимные функции также могут иметь несколько условий. Все условия должны ожидать одинаковое количество аргументов:
iex> negate = fn ...> true -> false ...> false -> true ...> end iex> negate.(false) true
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 и :uniq
В примерах выше результатом понимания всегда был список. Результат можно настроить, передав опцию :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
Аналогично, uniq: true также можно передавать в понимания, чтобы гарантировать, что результаты добавляются в коллекцию только в том случае, если они ранее не возвращались. Например:
iex> for x <- [1, 1, 2, 3], uniq: true, do: x * 2 [2, 4, 6] iex> for <<x <- "abcabc">>, uniq: true, into: "", do: <<x - 32>> "ABC"
Опция :reduce
В то время как опция :into позволяет настраивать поведение понимания для определённого типа данных, например, помещая все значения в карту или двоичную строку, этого часто недостаточно.
Например, представьте, что у вас есть двоичная строка с буквами, где вы хотите посчитать, сколько раз встречается каждая строчная буква, игнорируя все заглавные. Например, для строки "AbCabCABc" мы хотим получить карту %{"a" => 1, "b" => 2, "c" => 1}.
Если бы мы использовали :into, нам понадобился бы тип данных, который вычисляет частоту каждого элемента. В Elixir такого типа данных нет, вы могли бы реализовать его самостоятельно.
Проще использовать понимания для отображения и фильтрации букв, а затем вызвать Enum.reduce/3 для создания карты, например:
iex> letters = for <<x <- "AbCabCABc">>, x in ?a..?z, do: <<x>>
iex> Enum.reduce(letters, %{}, fn x, acc -> Map.update(acc, x, 1, & &1 + 1) end)
%{"a" => 1, "b" => 2, "c" => 1} Хотя это просто, у этого есть недостаток: данные просматриваются как минимум дважды. Если вы ожидаете длинные входные строки, это может быть довольно дорого.
К счастью, понимания также поддерживают опцию :reduce, которая позволила бы нам объединить оба шага в один:
iex> for <<x <- "AbCabCABc">>, x in ?a..?z, reduce: %{} do
...> acc -> Map.update(acc, <<x>>, 1, & &1 + 1)
...> end
%{"a" => 1, "b" => 2, "c" => 1} Когда указан ключ :reduce, его значение используется в качестве начального накопителя, а блок do должен быть изменён на использование условий ->, где левая часть -> получает накопленное значение предыдущей итерации, а выражение справа должно возвращать новое значение накопителя. Как только все элементы будут обработаны, возвращается окончательное накопленное значение. Если элементов нет, возвращается начальное значение накопителя.
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]} AST Elixir (Абстрактное синтаксическое дерево)
Любой код Elixir может быть представлен с помощью структур данных Elixir. Основной блок макросов Elixir – кортеж из трёх элементов, например:
{:sum, [], [1, 2, 3]} Кортеж выше представляет вызов функции sum с аргументами 1, 2 и 3. Элементы кортежа:
-
Первый элемент кортежа всегда атом или другой кортеж в том же представлении.
-
Второй элемент кортежа представляет метаданные.
-
Третий элемент кортежа – аргументы вызова функции. Третий аргумент может быть атомом, который обычно является переменной (или локальным вызовом).
Помимо описанного выше кортежа, Elixir имеет несколько литералов, которые также являются частью его AST. Эти литералы возвращают сами себя при цитировании. Это:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1, 2] #=> Lists
"strings" #=> Strings
{key, value} #=> Tuples with two elements Любое другое значение, такое как карта или кортеж из четырёх элементов, должно быть экранировано (Macro.escape/1) перед введением в AST.
Опции
-
:unquote- приfalse, отключает разцитирование. Это означает, что любой вызовunquoteбудет сохранён как есть в AST, вместо замены на аргументыunquote. Например:iex> quote do ...> unquote("hello") ...> end "hello" iex> quote unquote: false do ...> unquote("hello") ...> end {:unquote, [], ["hello"]} -
:location- при значении:keep, сохраняет текущую строку и файл из цитирования. Подробности о информации об отслеживании стека см. ниже. -
:line- устанавливает для цитируемых выражений указанную строку. -
:generated- отмечает данный фрагмент как сгенерированный, чтобы не выводить предупреждений. В настоящее время это работает только со специальными формами (например, вы можете аннотироватьcase, но неif). -
:context- устанавливает контекст разрешения. -
:bind_quoted- передаёт связывание макросу. При передаче связыванияunquote/1автоматически отключается.
Цитирование и макросы
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
Заметьте, как «Returning 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 Теперь вызов squared(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)
(macro)Проверяет, есть ли сообщение, соответствующее заданным условиям, в почтовом ящике текущего процесса.
В случае отсутствия такого сообщения текущий процесс приостанавливается до тех пор, пока не придёт сообщение или не истечёт заданное значение таймаута.
Примеры
receive do
{:selector, number, name} when is_integer(number) ->
name
name when is_atom(name) ->
name
_ ->
IO.puts :stderr, "Unexpected message received"
end Дополнительное after условие может быть задано в случае, если сообщение не было получено после истечения заданного времени ожидания, указанного в миллисекундах:
receive do
{:selector, number, name} when is_integer(number) ->
name
name when is_atom(name) ->
name
_ ->
IO.puts :stderr, "Unexpected message received"
after
5000 ->
IO.puts :stderr, "No message in 5 seconds"
end Условие after может быть указано даже если нет условий соответствия. Значение таймаута, задаваемое after, может быть любым выражением, результатом вычисления которого является одно из разрешённых значений:
-
:infinity- процесс должен неопределённо ждать соответствующего сообщения, это то же самое, что и не использовать условие после -
0- если в почтовом ящике нет соответствующего сообщения, таймаут наступит немедленно -
положительное целое число, меньшее или равное
4_294_967_295(0xFFFFFFFFв шестнадцатеричной записи) - должно быть возможно представить значение таймаута как беззнаковое 32-битное целое число.
Обработка переменных
Специальная форма receive/1 обрабатывает переменные точно так же, как специальный макрос case/2. Для получения дополнительной информации ознакомьтесь с документацией для case/2.
require(module, opts)
(macro)Загружает модуль для использования его макросов.
Примеры
Общедоступные функции в модулях доступны глобально, но для использования макросов вам необходимо разрешить это, загрузив модуль, в котором они определены.
Предположим, вы создали собственную реализацию if/2 в модуле MyMacros. Если вы хотите вызвать её, вам необходимо сначала явно загрузить модуль MyMacros:
defmodule Math do require MyMacros MyMacros.if do_something, it_works end
Попытка вызвать макрос, который не был загружен, приведёт к ошибке.
Сокращение псевдонима
require/2 также принимает :as в качестве опции, поэтому автоматически устанавливает псевдоним. Дополнительную информацию см. в alias/2.
super(args)
(macro)Вызывает переопределённую функцию при её переопределении с помощью 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 #{inspect(value)}"
else
value ->
IO.puts "Success! The result was #{inspect(value)}"
after
IO.puts "This is printed regardless if it failed or succeed"
end Условие rescue используется для обработки исключений, а условие catch может быть использовано для перехвата брошенных значений и завершений. Условие else может быть использовано для управления потоком на основе результата выражения. catch, rescue, и else условия работают на основе сопоставления с образцом (аналогично специальной форме case).
Вызовы внутри try/1 не являются хвостовой рекурсией, так как виртуальной машине необходимо сохранять стек отслеживания в случае возникновения исключения. Для получения стека отслеживания обратитесь к __STACKTRACE__/0 внутри условия rescue или catch.
rescue условия
Помимо использования сопоставления с образцом, rescue условия обеспечивают удобство работы с исключениями, что позволяет перехватывать исключение по его имени. Все следующие форматы являются допустимыми шаблонами в rescue условиях:
# Rescue a single exception without binding the exception # to a variable try do UndefinedModule.undefined_function rescue UndefinedFunctionError -> nil end # Rescue any of the given exception without binding try do UndefinedModule.undefined_function rescue [UndefinedFunctionError, ArgumentError] -> nil end # Rescue and bind the exception to the variable "x" try do UndefinedModule.undefined_function rescue x in [UndefinedFunctionError] -> nil end # Rescue all kinds of exceptions and bind the rescued exception # to the variable "x" try do UndefinedModule.undefined_function rescue x -> nil end
Ошибки Erlang
Ошибки Erlang преобразуются в ошибки Elixir при перехвате:
try do :erlang.error(:badarg) rescue ArgumentError -> :ok end #=> :ok
Наиболее распространённые ошибки Erlang преобразуются в их аналоги Elixir. Те, которые не преобразуются, будут преобразованы в более общую ошибку ErlangError:
try do :erlang.error(:unknown) rescue ErlangError -> :ok end #=> :ok
На самом деле, ErlangError может использоваться для перехвата любой ошибки, которая не является правильной ошибкой Elixir. Например, её можно использовать для перехвата ранее описанной ошибки :badarg тоже, до преобразования:
try do :erlang.error(:badarg) rescue ErlangError -> :ok end #=> :ok
catch условия
Условие catch может использоваться для перехвата брошенных значений, завершений и ошибок.
Перехват брошенных значений
catch может быть использовано для перехвата значений, брошенных с помощью Kernel.throw/1:
try do
throw(:some_value)
catch
thrown_value ->
IO.puts "A value was thrown: #{inspect(thrown_value)}"
end Перехват значений любого типа
Условие catch также поддерживает перехват завершений и ошибок. Для этого оно позволяет сопоставлять с образцом как тип перехваченного значения, так и само значение:
try do
exit(:shutdown)
catch
:exit, value
IO.puts "Exited with value #{inspect(value)}"
end
try do
exit(:shutdown)
catch
kind, value when kind in [:exit, :throw] ->
IO.puts "Caught exit or throw with value #{inspect(value)}"
end Условие catch также поддерживает :error наряду с :exit и :throw, как в Erlang, хотя это обычно избегается в пользу механизмов управления raise/rescue. Одна из причин этого заключается в том, что при перехвате :error, ошибка не преобразуется автоматически в ошибку Elixir:
try do :erlang.error(:badarg) catch :error, :badarg -> :ok end #=> :ok
after условия
Условие after позволяет определить логику очистки, которая будет вызвана как в случае успешного выполнения блока кода, переданного в try/1, так и в случае возникновения ошибки. Обратите внимание, что процесс завершит свою работу обычным способом при получении сигнала завершения, который заставляет его выйти внезапно, поэтому выполнение условия 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 условия позволяют сопоставить с образцом результат тела, переданного в try/1:
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 cannot be rescued here:
TryClauseError ->
:error_a
else
0 ->
:small
end
rescue
# The TryClauseError is rescued here:
TryClauseError ->
:error_b
end Аналогично, исключение внутри условия else не перехватывается или обрабатывается внутри того же try:
try do
try do
nil
catch
# The exit(1) call below can not be caught here:
:exit, _ ->
:exit_a
else
_ ->
exit(1)
end
catch
# The exit is caught here:
:exit, _ ->
:exit_b
end Это означает, что виртуальная машина больше не нуждается в сохранении стека отслеживания после попадания в условие else, и поэтому возможна хвостовая рекурсия при использовании try с вызовом хвоста в качестве конечного вызова внутри условия else. То же самое верно для условий rescue и catch.
Только результат пробованного выражения передаётся в условие else . Если try оказывается в условиях rescue или catch, их результат не перейдёт в else:
try do
throw(:catch_this)
catch
:throw, :catch_this ->
:it_was_caught
else
# :it_was_caught will not fall down to this "else" clause.
other ->
{:else, other}
end Обработка переменных
Поскольку выражение внутри try может не быть вычислено из-за исключения, любая переменная, созданная внутри try, не может быть обращена снаружи. Например:
try do x = 1 do_something_that_may_fail(same_arg) :ok catch _, _ -> :failed end x #=> unbound variable "x"
В приведённом выше примере x недоступна, поскольку она была определена внутри условия try. Распространённый способ решения этой проблемы — вернуть переменные, определённые внутри try:
x =
try do
x = 1
do_something_that_may_fail(same_arg)
x
catch
_, _ -> :failed
end unquote(expr)
(macro)Отключает кавычки из заданного выражения внутри выражения в кавычках.
Эта функция ожидает корректный AST Elixir, также известный как выражение в кавычках, в качестве аргумента. Если вам необходимо unquote любое значение, такое как карта или четырёхэлементная кортеж, вы должны вызвать Macro.escape/1 перед отсоединением от кавычек.
Примеры
Представьте себе ситуацию, когда у вас есть выражение в кавычках, и вы хотите вставить его внутрь цитаты. Первая попытка будет:
value =
quote do
13
end
quote do
sum(1, value, 3)
end Что затем вернёт:
{:sum, [], [1, {:value, [], Elixir}, 3]} Что не является ожидаемым результатом. Для этого мы используем unquote:
iex> value =
...> quote do
...> 13
...> end
iex> quote do
...> sum(1, unquote(value), 3)
...> end
{:sum, [], [1, 13, 3]} Если вам нужно отсоединиться от кавычек значения, которое не является выражением в кавычках, например, карты, вам необходимо вызвать Macro.escape/1 перед этим:
iex> value = %{foo: :bar}
iex> quote do
...> process_map(unquote(Macro.escape(value)))
...> end
{:process_map, [], [{:%{}, [], [foo: :bar]}]} Если вы забудете его экранировать, Elixir выдаст ошибку при компиляции кода.
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}
...> end
{: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}
...> end
: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)}
...> end
{: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}
...> end
{:ok, 300}
iex> width
nil Поведение любого выражения в правиле такое же, как и вне его. Например, = сгенерирует MatchError, а не вернёт несопоставленное значение:
with :foo = :bar, do: :ok #=> ** (MatchError) no match of right hand side value: :bar
Как и при любом другом вызове функции или макроса в Elixir, явные скобки также могут быть использованы вокруг аргументов перед блоком do/end:
iex> opts = %{width: 10, height: 15}
iex> with(
...> {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height)
...> ) do
...> {:ok, width * height}
...> end
{:ok, 150} Выбор между скобками и без них зависит от предпочтений.
Можно использовать опцию 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.8.2/Kernel.SpecialForms.html