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 [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}
-
Создаёт кортеж
Функции
%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" END_OF_DOCUMENT_MARKER
```В данном примере выше мы использовали . для вызова 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 предназначены для кодовых точек 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, 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 по работе с бинарными данными.
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.
__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 с каждым условием «заголовок» и выполняем тело условия, соответствующее первому условию, которое совпадает.
Если ни одно условие не соответствует, возникает ошибка. По этой причине может потребоваться добавить окончательное условие catch-all (например, _), которое всегда будет соответствовать.
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
Анонимные функции также могут иметь несколько условий. Все условия должны ожидать одинаковое количество аргументов:
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
В приведённых выше примерах возвращаемым результатом понимания всегда был список. Возвращаемый результат можно настроить, передав опцию :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
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"
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, сохраняет текущую строку и файл из цитаты. Более подробную информацию см. в разделе «Информация о трассировке стека» ниже. -
:line- устанавливает строку для процитированных выражений. -
: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 Теперь вызов 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) (макрос)
Проверяет наличие сообщения, соответствующего заданным предложениям в почтовом ящике текущего процесса.
В случае отсутствия такого сообщения текущий процесс зависает до тех пор, пока не придёт сообщение, или ждёт до заданного значения таймаута.
Примеры
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— процесс должен неопределённо долго ожидать сообщения, соответствующего шаблону, это то же самое, что и отсутствие оператора after -
0— если в почтовом ящике нет соответствующего сообщения, таймаут произойдёт немедленно -
положительное целое число, меньшее или равное
4_294_967_295(0xFFFFFFFFв шестнадцатеричной записи) — значение таймаута должно быть представимо как беззнаковое 32-битовое целое число.
Обработка переменных
Специальная форма receive/1 обрабатывает переменные точно так же, как и специальный макрос case/2. Более подробную информацию см. в документации по case/2.
require(module, opts) (макрос)
Требует модуль для использования его макросов.
Примеры
Общедоступные функции в модулях доступны глобально, но для использования макросов необходимо включить их, потребовав модуль, в котором они определены.
Предположим, что вы создали свою собственную реализацию if/2 в модуле MyMacros. Если вы хотите вызвать её, вам необходимо сначала явно потребовать модуль MyMacros:
defmodule Math do require MyMacros MyMacros.if do_something, it_works end
Попытка вызвать макрос, который не загружен, приведёт к ошибке.
Сокращение с псевдонимом
require/2 также принимает as: в качестве опции, поэтому автоматически создаёт псевдоним. Более подробную информацию см. в alias/2.
super(args) (макрос)
Вызывает переопределённую функцию при её переопределении с помощью Kernel.defoverridable/1.
См. Kernel.defoverridable/1 для получения дополнительной информации и документации.
try(args) (макрос)
Вычисляет заданные выражения и обрабатывает любые ошибки, завершения или выбрасывания, которые могли произойти.
Примеры
try do
do_something_that_may_fail(some_arg)
rescue
ArgumentError ->
IO.puts "Invalid argument given"
catch
value ->
IO.puts "Caught #{inspect(value)}"
else
value ->
IO.puts "Success! The result was #{inspect(value)}"
after
IO.puts "This is printed regardless if it failed or succeed"
end Оператор rescue используется для обработки исключений, а оператор catch может быть использован для перехвата сбрасываемых значений и завершений. Оператор else может использоваться для управления потоком выполнения на основе результата выражения. catch, rescue, и else операторы работают на основе сопоставления с образцом (аналогично специальной форме case).
Обратите внимание, что вызовы внутри try/1 не являются рекурсивными по хвосту, так как виртуальной машине необходимо сохранять трассировку стека на случай возникновения исключения. Для получения трассировки стека обратитесь к __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) (макрос)
Выводит указанное выражение из макроса.
Примеры
Представьте ситуацию, когда у вас есть переменная value и вы хотите вставить её в некоторую строку. Первая попытка будет:
value = 13 quote do sum(1, value, 3) end
Что вернёт:
{:sum, [], [1, {:value, [], quoted}, 3]} Что не является ожидаемым результатом. Для этого используется unquote:
iex> value = 13
iex> quote do
...> sum(1, unquote(value), 3)
...> end
{:sum, [], [1, 13, 3]} unquote_splicing(expr) (макрос)
Выводит заданный список, расширяя его аргументы. Аналогично unquote/1.
Примеры
iex> values = [2, 3, 4]
iex> quote do
...> sum(1, unquote_splicing(values), 5)
...> end
{:sum, [], [1, 2, 3, 4, 5]} with(args) (макрос)
Используется для объединения операторов соответствия.
Начнём с примера:
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height) do
...> {:ok, width * height}
...> 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.7.4/Kernel.SpecialForms.html