Kernel.SpecialForms
Специальные формы — это базовые строительные блоки Elixir, и поэтому не могут быть переопределены разработчиком.
Модуль Kernel.SpecialForms состоит только из макросов, которые можно вызывать в любом месте кода Elixir без использования префикса Kernel.SpecialForms.. Это возможно, потому что все они автоматически импортированы, так же, как функции и макросы из модуля Kernel.
В этом модуле определены эти строительные блоки. Некоторые из этих специальных форм являются лексическими (такие как alias/2 и case/2). Макросы {}/1 и <<>>/1 также являются специальными формами, используемыми для определения кортежей и двоичных структур данных соответственно.
В этом модуле также документированы макросы, возвращающие информацию об окружении компиляции Elixir, такие как (__ENV__/0, __MODULE__/0, __DIR__/0, __STACKTRACE__/0 и __CALLER__/0).
Кроме того, он документирует две специальные формы, __block__/1 и __aliases__/1, которые не предназначены для прямого вызова разработчиком, но они появляются в цитируемом содержимом, поскольку они являются необходимыми конструкциями в Elixir.
Краткое описание
Функции
- %struct{}
Сопоставляет или создаёт структуру.
- %{}
Создаёт карту.
- &expr
Оператор захвата. Захватывает или создаёт анонимную функцию.
- left . right
Оператор точки. Определяет удалённый вызов, вызов анонимной функции или псевдоним.
- __aliases__(args)
Внутренняя специальная форма для хранения информации о псевдонимах.
- __block__(args)
Внутренняя специальная форма для блочных выражений.
- __CALLER__
Возвращает текущую вызывающую среду как структуру
Macro.Env.- __DIR__
Возвращает абсолютный путь к каталогу текущего файла как двоичную строку.
- __MODULE__
Возвращает имя текущего модуля как атом или
nilв противном случае.- __STACKTRACE__
Возвращает стек вызовов для текущего обработанного исключения.
- left :: right
Оператор типа. Используется типами и битовыми строками для указания типов.
- <<args>>
Определяет новую битовую строку.
- left = right
Оператор сопоставления. Сопоставляет значение справа с шаблоном слева.
- alias(module, opts)
alias/2используется для установки псевдонимов, что часто полезно с именами модулей.- case(condition, clauses)
Сопоставляет данное выражение с заданными условиями.
- cond(clauses)
Вычисляет выражение, соответствующее первому условию, которое вычисляет истинное значение.
- fn(clauses)
Определяет анонимную функцию.
- 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)
Используется для объединения условий сопоставления.
- ^var
Оператор закрепления. Доступ к уже связанной переменной в условиях сопоставления.
- {args}
Создаёт кортеж.
Функции
%struct{}Source
Сопоставление с структурой или создание структуры.
Структура — это помеченная карта, которая позволяет разработчикам задавать значения по умолчанию для ключей, использовать метки для полиморфных диспетчеризаций и проверок на этапе компиляции.
Структуры обычно определяются с помощью макроса 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 %{}Source
Создает карту.
Дополнительную информацию о картах, их синтаксисе, способах доступа к ним и их манипулировании см. в модуле Map.
Представление AST
Независимо от того, используется ли => или синтаксис ключевых слов, пары ключ-значение в картах всегда представляются во внутренней форме как список пар кортежей из двух элементов для простоты:
iex> quote do
...> %{"a" => :b, c: :d}
...> end
{:%{}, [], [{"a", :b}, {:c, :d}]} &exprSource
Оператор захвата. Захватывает или создает анонимную функцию.
Захват
Оператор захвата чаще всего используется для захвата функции с заданным именем и арностью из модуля:
iex> fun = &Kernel.is_atom/1
iex> fun.(:atom)
true
iex> fun.("string")
false
В приведенном выше примере мы захватили Kernel.is_atom/1 как анонимную функцию, а затем вызываем ее.
Оператор захвата также можно использовать для захвата локальных функций, в том числе и частных, а также импортированных функций, опуская имя модуля:
&local_function/1
См. также Function.capture/3.
Анонимные функции
Оператор захвата также можно использовать для частичного применения функций, где &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 . rightSource
Оператор точки. Определяет удаленный вызов, вызов анонимной функции или псевдоним.
Точка (.) в 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]} __aliases__(args)Source
Внутренняя специальная форма для хранения информации о псевдонимах.
Обычно компилируется в атом:
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)Source
Внутренняя специальная форма для выражений с блоками.
Это специальная форма, используемая при наличии блока выражений в Elixir. Эта специальная форма является частной и не должна вызываться напрямую:
iex> quote do
...> 1
...> 2
...> 3
...> end
{:__block__, [], [1, 2, 3]} __CALLER__Source
Возвращает текущую среду вызова в виде структуры Macro.Env.
В среде вызова можно получить доступ к имени файла, номерам строк, настроить псевдонимы, функцию и другие данные.
__DIR__Source
Возвращает абсолютный путь к каталогу текущего файла в виде бинарного значения.
Хотя к каталогу можно получить доступ как к Path.dirname(__ENV__.file), этот макрос является удобным сокращением.
__ENV__Source
Возвращает текущую информацию об окружении в виде структуры Macro.Env.
В окружении можно получить доступ к текущему имени файла, номерам строк, настроить псевдонимы, текущую функцию и другие данные.
__MODULE__Source
Возвращает текущее имя модуля как атом или nil в противном случае.
Хотя к модулю можно получить доступ в __ENV__/0, эта макрокоманда является удобным сокращением.
__STACKTRACE__Source
Возвращает стек вызовов для текущего обрабатываемого исключения.
Доступно только в catch и rescue разделах выражений try/1.
Для получения стека вызовов текущего процесса используйте Process.info(self(), :current_stacktrace).
left :: rightSource
Оператор типа. Используется типами и битовыми строками для указания типов.
Этот оператор используется в Elixir в двух разных случаях. Он используется в описаниях типов для указания типа переменной, функции или самого типа:
@type number :: integer | float @spec add(number, number) :: number
Он также может использоваться в битовых строках для указания типа заданного битового сегмента:
<<int::integer-little, rest::bits>> = bits
Прочитайте документацию на странице Typespec и <<>>/1 для получения дополнительной информации о типах и битовых строках соответственно.
<<args>>Source
Определяет новую битовую строку.
Примеры
iex> <<1, 2, 3>> <<1, 2, 3>>
Типы
Битовая строка состоит из множества сегментов, и каждый сегмент имеет тип. Существует 9 типов, используемых в битовых строках:
integerfloat-
bits(псевдоним дляbitstring) bitstringbinary-
bytes(псевдоним дляbinary) utf8utf16utf32
Если тип не указан, по умолчанию используется integer:
iex> <<1, 2, 3>> <<1, 2, 3>>
Elixir также по умолчанию принимает сегмент как литеральную строку или литеральную последовательность символов, которые по умолчанию преобразуются в целые числа:
iex> <<0, "foo">> <<0, 102, 111, 111>>
Бинарные данные необходимо явно пометить как 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>>
В противном случае при создании двоичных данных возникает ошибка ArgumentError:
rest = "oo" <<102, rest>> ** (ArgumentError) argument error
Параметры
Многие параметры могут быть заданы, используя - в качестве разделителя. Порядок произвольный, поэтому следующие варианты эквивалентны:
<<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 должно быть равно 16, 32 или 64, что соответствует IEEE 754 binary16, 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.
left = rightSource
Оператор совпадения. Сопоставляет значение справа с шаблоном слева.
alias(module, opts)Source
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)Source
Сопоставляет данное выражение с заданными условиями.
Примеры
case File.read(file) do
{:ok, contents} when is_binary(contents) ->
String.split(contents, "\n")
{:error, _reason} ->
Logger.warning "could not find #{file}, assuming empty..."
[]
end
В примере выше, мы сопоставляем результат File.read/1 с каждым условием «заголовок» и выполняем тело условия, соответствующее первому совпавшему условию.
Если ни одно условие не совпадает, генерируется ошибка. По этой причине может потребоваться добавить окончательное условие (например, _) которое всегда будет соответствовать.
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
В примере выше, value будет 7 независимо от значения lucky?. Переменная value, связанная в условии, и переменная value, связанная во внешнем контексте, являются совершенно разными переменными.
Если вы хотите сопоставить образец с существующей переменной, вам нужно использовать оператор ^/1:
x = 1 case 10 do ^x -> "Won't match" _ -> "Will match" end #=> "Will match"
Использование защит для сопоставления с несколькими значениями
Хотя невозможно сопоставить несколько шаблонов в одном условии, можно сопоставить несколько значений, используя защитные условия:
case data do
value when value in [:one, :two] ->
"#{value} has been matched"
:three ->
"three has been matched"
end cond(clauses)Source
Вычисляет выражение, соответствующее первому условию, которое вычисляется как истинное.
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)Source
Определяет анонимную функцию.
См. Function для получения дополнительной информации.
Примеры
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)Source
Построение данных из перечислимого объекта или битовой строки.
Начнём с примера:
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(), into: IO.stream() 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)Source
Импортирует функции и макросы из других модулей.
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)Source
Получает представление любого выражения.
Примеры
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.
Параметры
:bind_quoted— передаёт связывание макросу. Когда предоставляется связывание,unquote/1автоматически отключается.:context— устанавливает контекст разрешения.:generated— помечает заданный фрагмент как сгенерированный, чтобы не генерировать предупреждения. В настоящее время он работает только со специальными формами (например, вы можете аннотироватьcase, но неif).:file— устанавливает, что цитируемые выражения должны иметь указанный файл.:line— устанавливает, что цитируемые выражения должны иметь указанную строку.:location— при значении:keep, сохраняет текущую строку и файл из quote. Подробную информацию см. в разделе «Информация об отладке стека» ниже.-
:unquote— при значенииfalse, отключает перефразирование. Это означает, что любой вызовunquoteбудет сохранён в AST как есть, а не заменён аргументамиunquote. Например:iex> quote do ...> unquote("hello") ...> end "hello" iex> quote unquote: false do ...> unquote("hello") ...> end {:unquote, [], ["hello"]}
Цитирование и макросы
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
Гигиена псевдонимов
Псевдонимы внутри quote по умолчанию гигиеничны. Рассмотрим следующий пример:
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]) внутри quote, но функция 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
Информация об отладке стека
При определении функций с помощью макросов разработчики могут выбрать, будут ли ошибки времени выполнения сообщаться из вызывающего модуля или изнутри quote. Посмотрим пример:
# 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, информация об отладке стека будет указывать на файл и строку внутри quote. Без location: :keep, ошибка сообщается в том месте, где был вызван defadd . location: :keep влияет только на определения внутри quote.
Важно: не используйте location: :keep, если определение функции также
unquoteнекоторые из аргументов макроса. В противном случае Elixir сохранит определение файла текущего местоположения, но нецитируемые аргументы могут содержать информацию о строке вызывающего макроса, что приведёт к ошибочной отладке стека.
Связывание и фрагменты перефразирования
Механизмы цитирования/перефразирования 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)Source
Проверяет наличие сообщения, соответствующего заданным условиям в почтовом ящике текущего процесса.
В случае отсутствия такого сообщения текущий процесс приостанавливается до тех пор, пока не придёт сообщение или не истечёт заданное время ожидания.
Примеры
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)Source
Загружает модуль в качестве компиляционной зависимости.
Загрузка модуля необходима для использования его макросов.
Примеры
Общедоступные функции в модулях глобально доступны, но для использования макросов необходимо явно загрузить модуль, в котором они определены.
Предположим, вы создали собственную реализацию if/2 в модуле MyMacros. Если вы хотите её вызвать, вам нужно сначала загрузить MyMacros:
defmodule Math do require MyMacros MyMacros.if do_something, it_works end
Попытка вызвать макрос, который не загружен, приведёт к ошибке.
Сокращение для алиасов
require/2 также принимает :as в качестве опции, чтобы автоматически установить алиас. Для получения дополнительной информации см. alias/2.
super(args)Source
Вызывает переопределённую функцию при её переопределении с помощью Kernel.defoverridable/1.
См. Kernel.defoverridable/1 для получения дополнительной информации и документации.
try(args)Source
Вычисляет заданные выражения и обрабатывает любые ошибки, выходы или броски, которые могут произойти.
Примеры
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 succeeded")
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)Source
Дезактивирует заданное выражение внутри цитируемого выражения.
Эта функция ожидает валидный 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)Source
Разворачивает переданный список, расширяя его аргументы.
Аналогично 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)Source
Используется для объединения правил сопоставления.
Начнём с примера:
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
Поведение любого выражения в правиле такое же, как если бы оно было записано вне with. Например, = вызовет исключение 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
Опция 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}
...>
...> _other_error ->
...> :unexpected_error
...> end
{:error, :wrong_data}
Блок else работает как правило case: он может иметь несколько правил, и будет использовано первое совпадение. Переменные, объявленные внутри with (например, width в этом примере), недоступны в блоке else.
Если используется блок else и нет совпадающих правил, генерируется исключение WithClauseError.
Внимание!
Помните, что одним из потенциальных недостатков with является то, что все правила обработки ошибок сгруппированы в единый блок else. Например, рассмотрим этот код, который проверяет, указывает ли заданный путь на файл Elixir, и существует ли он до создания резервной копии:
with ".ex" <- Path.extname(path),
true <- File.exists?(path) do
backup_path = path <> ".backup"
File.cp!(path, backup_path)
{:ok, backup_path}
else
binary when is_binary(binary) ->
{:error, :invalid_extension}
false ->
{:error, :missing_file}
end
Обратите внимание, как нам нужно перестраивать типы результатов Path.extname/1 и File.exists?/1 для построения сообщений об ошибках. В этом случае лучше изменить правила with так, чтобы они возвращали желаемый формат, например так:
with :ok <- validate_extension(path),
:ok <- validate_exists(path) do
backup_path = path <> ".backup"
File.cp!(path, backup_path)
{:ok, backup_path}
end
defp validate_extname(path) do
if Path.extname(path) == ".ex", do: :ok, else: {:error, :invalid_extension}
end
defp validate_exists(path) do
if File.exists?(path), do: :ok, else: {:error, :missing_file}
end
Обратите внимание, как код выше более организован и понятен, когда каждое правило в with возвращает нормализованный формат.
^varSource
Оператор привязки. Доступ к уже связанной переменной в правилах сопоставления.
Примеры
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 {args}Source
Создаёт кортеж.
Дополнительную информацию о типе данных кортеж и функциях для работы с кортежами можно найти в модуле 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.13.4/Kernel.SpecialForms.html