Эликсир - Краткое руководство

Elixir - это динамический функциональный язык, разработанный для создания масштабируемых и поддерживаемых приложений. Он использует виртуальную машину Erlang, известную тем, что запускает распределенные и отказоустойчивые системы с малой задержкой, а также успешно используется в веб-разработке и области встроенного программного обеспечения.

Elixir - это функциональный, динамический язык, построенный на основе Erlang и Erlang VM. Erlang - это язык, который изначально был написан Эрикссон в 1986 году для решения таких проблем телефонии, как распределение, отказоустойчивость и параллелизм. Elixir, написанный Хосе Валимом, расширяет Erlang и обеспечивает более удобный синтаксис в Erlang VM. Он делает это, сохраняя при этом производительность на том же уровне, что и Erlang.

Особенности Эликсира

Давайте теперь обсудим несколько важных функций Elixir -

  • Scalability - Весь код Elixir выполняется внутри легких процессов, которые изолированы и обмениваются информацией через сообщения.

  • Fault Tolerance- В Elixir есть средства контроля, которые описывают, как перезапускать части вашей системы, когда что-то идет не так, возвращаясь к известному начальному состоянию, которое гарантированно работает. Это гарантирует, что ваше приложение / платформа никогда не выйдет из строя.

  • Functional Programming - Функциональное программирование продвигает стиль кодирования, который помогает разработчикам писать короткий, быстрый и удобный в обслуживании код.

  • Build tools- Эликсир поставляется с набором инструментов для разработки. Mix - один из таких инструментов, который упрощает создание проектов, управление задачами, запуск тестов и т. Д. У него также есть собственный менеджер пакетов - Hex.

  • Erlang Compatibility - Elixir работает на виртуальной машине Erlang, предоставляя разработчикам полный доступ к экосистеме Erlang.

Чтобы запустить Elixir, вам необходимо настроить его локально в вашей системе.

Чтобы установить Elixir, вам сначала потребуется Erlang. На некоторых платформах пакеты Elixir содержат Erlang.

Установка Эликсира

Давайте теперь разберемся с установкой Elixir в разных операционных системах.

Установка Windows

Чтобы установить Elixir на Windows, загрузите установщик с https://repo.hex.pm/elixirwebsetup.exe и просто нажмите Nextпройти через все шаги. Он будет у вас в локальной системе.

Если у вас возникли проблемы при его установке, вы можете проверить эту страницу для получения дополнительной информации.

Настройка Mac

Если у вас установлен Homebrew, убедитесь, что это последняя версия. Для обновления используйте следующую команду -

brew update

Теперь установите Elixir, используя команду, указанную ниже -

brew install elixir

Установка Ubuntu / Debian

Шаги по установке Elixir в установке Ubuntu / Debian следующие:

Добавить репозиторий Erlang Solutions -

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo 
dpkg -i erlang-solutions_1.0_all.deb 
sudo apt-get update

Установите платформу Erlang / OTP и все ее приложения -

sudo apt-get install esl-erlang

Установить Эликсир -

sudo apt-get install elixir

Другие дистрибутивы Linux

Если у вас есть другой дистрибутив Linux, посетите эту страницу, чтобы установить elixir в вашей локальной системе.

Тестирование установки

Чтобы протестировать установку Elixir в вашей системе, откройте терминал и введите в нем iex. Он откроет интерактивную оболочку эликсира следующим образом:

Erlang/OTP 19 [erts-8.0] [source-6dc93c1] [64-bit] 
[smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]  

Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help) 
iex(1)>

Эликсир теперь успешно настроен в вашей системе.

Начнем с привычной программы Hello World.

Чтобы запустить интерактивную оболочку Elixir, введите следующую команду.

iex

После запуска оболочки используйте IO.putsфункция для "помещения" строки в вывод консоли. Введите в оболочку Elixir следующее:

IO.puts "Hello world"

В этом руководстве мы будем использовать режим сценария Эликсира, в котором мы будем хранить код Эликсира в файле с расширением .ex. Давайте теперь сохраним приведенный выше код вtest.exфайл. На следующем шаге мы выполним его, используяelixirc-

IO.puts "Hello world"

Давайте теперь попробуем запустить вышеуказанную программу следующим образом -

$elixirc test.ex

Вышеупомянутая программа генерирует следующий результат -

Hello World

Здесь мы вызываем функцию IO.putsчтобы сгенерировать строку для нашей консоли в качестве вывода. Эту функцию также можно вызвать так, как мы это делаем в C, C ++, Java и т. Д., Предоставляя аргументы в скобках после имени функции -

IO.puts("Hello world")

Комментарии

Однострочные комментарии начинаются с символа «#». Многострочного комментария нет, но вы можете складывать несколько комментариев. Например -

#This is a comment in Elixir

Окончание строк

Нет обязательных окончаний строк, таких как ';' в Эликсире. Однако у нас может быть несколько операторов в одной строке, используя ';'. Например,

IO.puts("Hello"); IO.puts("World!")

Вышеупомянутая программа генерирует следующий результат -

Hello 
World!

Идентификаторы

Идентификаторы, такие как переменные, имена функций, используются для идентификации переменных, функций и т. Д. В Elixir вы можете называть свои идентификаторы, начиная со строчного алфавита, с последующими числами, символами подчеркивания и прописными буквами. Это соглашение об именах обычно известно как snake_case. Например, ниже приведены некоторые действительные идентификаторы в Elixir:

var1       variable_2      one_M0r3_variable

Обратите внимание, что переменные также могут быть названы с начальным подчеркиванием. Значение, которое не предназначено для использования, должно быть присвоено _ или переменной, начинающейся с подчеркивания -

_some_random_value = 42

Также в elixir используются символы подчеркивания, чтобы сделать функции закрытыми для модулей. Если вы назовете функцию с ведущим подчеркиванием в модуле и импортируете этот модуль, эта функция не будет импортирована.

Есть еще много сложностей, связанных с именованием функций в Эликсире, которые мы обсудим в следующих главах.

Зарезервированные слова

Следующие слова зарезервированы и не могут использоваться в качестве имен переменных, модулей или функций.

after     and     catch     do     inbits     inlist     nil     else     end 
not     or     false     fn     in     rescue     true     when     xor 
__MODULE__    __FILE__    __DIR__    __ENV__    __CALLER__

Для использования любого языка вам необходимо понимать основные типы данных, которые поддерживает язык. В этой главе мы обсудим 7 основных типов данных, поддерживаемых языком эликсира: целые числа, числа с плавающей запятой, логические значения, атомы, строки, списки и кортежи.

Числовые типы

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

42

Когда вышеуказанная программа запускается, она дает следующий результат -

42

Вы также можете определять числа в восьмеричной, шестнадцатеричной и двоичной системе счисления.

Восьмеричный

Чтобы определить число в восьмеричной системе счисления, добавьте к нему префикс «0o». Например, 0o52 в восьмеричном формате эквивалентно 42 в десятичном.

Шестнадцатеричный

Чтобы определить число в десятичной системе счисления, добавьте к нему префикс «0x». Например, 0xF1 в шестнадцатеричном формате эквивалентно 241 в десятичном.

Двоичный

Чтобы определить число в двоичной системе счисления, добавьте к нему префикс «0b». Например, 0b1101 в двоичном формате эквивалентно 13 в десятичном.

Elixir поддерживает 64-битную двойную точность для чисел с плавающей запятой. И они также могут быть определены с использованием стиля возведения в степень. Например, 10145230000 можно записать как 1.014523e10

Атомы

Атомы - это константы, имя которых является их значением. Их можно создать с помощью цветного символа (:). Например,

:hello

Булевы

Эликсир поддерживает true и falseкак логические. Оба эти значения фактически привязаны к атомам: true и: false соответственно.

Струны

Строки в Elixir заключаются в двойные кавычки и кодируются в UTF-8. Они могут занимать несколько строк и содержать интерполяции. Чтобы определить строку, просто введите ее в двойные кавычки -

"Hello world"

Чтобы определить многострочные строки, мы используем синтаксис, похожий на Python с тройными двойными кавычками -

"""
Hello
World!
"""

Мы подробно узнаем о строках, двоичных файлах и списках символов (подобных строкам) в главе о строках.

Двоичные файлы

Двоичные файлы - это последовательности байтов, заключенные в << >>, разделенные запятой. Например,

<< 65, 68, 75>>

Двоичные файлы в основном используются для обработки данных, связанных с битами и байтами, если они у вас есть. По умолчанию они могут хранить в каждом значении от 0 до 255. Этот предел размера можно увеличить, используя функцию размера, которая говорит, сколько битов необходимо для хранения этого значения. Например,

<<65, 255, 289::size(15)>>

Списки

В Elixir для указания списка значений используются квадратные скобки. Ценности могут быть любого типа. Например,

[1, "Hello", :an_atom, true]

Списки имеют встроенные функции для заголовка и хвоста списка с именами hd и tl, которые возвращают начало и конец списка соответственно. Иногда, когда вы создаете список, он возвращает список символов. Это связано с тем, что, когда elixir видит список печатаемых символов ASCII, он печатает его как список символов. Обратите внимание, что строки и списки символов не равны. Мы обсудим списки далее в следующих главах.

Кортежи

В Elixir для определения кортежей используются фигурные скобки. Как и списки, кортежи могут содержать любое значение.

{ 1, "Hello", :an_atom, true

Здесь возникает вопрос, а зачем использовать оба lists и tuplesкогда они оба работают одинаково? Ну у них разные реализации.

  • Списки фактически хранятся как связанные списки, поэтому вставки и удаления в списках выполняются очень быстро.

  • С другой стороны, кортежи хранятся в непрерывном блоке памяти, что ускоряет доступ к ним, но добавляет дополнительные затраты на вставку и удаление.

Переменная предоставляет нам именованное хранилище, которым могут управлять наши программы. Каждая переменная в Elixir имеет определенный тип, который определяет размер и структуру памяти переменной; диапазон значений, которые могут быть сохранены в этой памяти; и набор операций, которые можно применить к переменной.

Типы переменных

Elixir поддерживает следующие основные типы переменных.

Целое число

Они используются для целых чисел. Они имеют размер 32 бита в 32-битной архитектуре и 64 бита в 64-битной архитектуре. Целые числа всегда подписываются в эликсире. Если целое число начинает увеличиваться в размере сверх своего предела, elixir преобразует его в большое целое число, которое занимает память в диапазоне от 3 до n слов, в зависимости от того, какое из них может поместиться в памяти.

Плавает

В эликсире поплавки имеют 64-битную точность. Они также похожи на целые числа с точки зрения памяти. При определении числа с плавающей запятой можно использовать экспоненциальную запись.

Булево

Они могут принимать 2 значения: истинное или ложное.

Струны

Строки закодированы в кодировке utf-8 в elixir. У них есть модуль строк, который предоставляет программисту множество функций для управления строками.

Анонимные функции / лямбды

Это функции, которые можно определить и присвоить переменной, которая затем может использоваться для вызова этой функции.

Коллекции

В Elixir доступно множество типов коллекций. Некоторые из них - это списки, кортежи, карты, двоичные файлы и т. Д. Они будут рассмотрены в следующих главах.

Объявление переменной

Объявление переменной сообщает интерпретатору, где и сколько создать хранилище для переменной. Эликсир не позволяет нам просто объявить переменную. Переменная должна быть объявлена ​​и присвоено значение одновременно. Например, чтобы создать переменную с именем life и присвоить ей значение 42, мы делаем следующее:

life = 42

Это будет связывать жизнь переменной на значение 42. Если мы хотим передать эту переменную новое значение, мы можем сделать это, используя тот же синтаксис, что и выше, т.е.

life = "Hello world"

Именование переменных

Именование переменных следует за snake_caseсоглашение в Elixir, т. е. все переменные должны начинаться со строчной буквы, за которой следует 0 или более букв (как в верхнем, так и в нижнем регистре), а в конце следует необязательный знак '?' ИЛИ ЖЕ '!'.

Имена переменных также могут начинаться с символа подчеркивания в начале, но его следует использовать только при игнорировании переменной, т. Е. Эта переменная больше не будет использоваться, а должна быть назначена чему-либо.

Печать переменных

В интерактивной оболочке переменные будут напечатаны, если вы просто введете имя переменной. Например, если вы создаете переменную -

life = 42

И введите 'жизнь' в свою оболочку, вы получите результат как -

42

Но если вы хотите вывести переменную на консоль (при запуске внешнего скрипта из файла), вам необходимо предоставить переменную в качестве входных данных для IO.puts функция -

life = 42  
IO.puts life

или же

life = 42 
IO.puts(life)

Это даст вам следующий результат -

42

Оператор - это символ, который сообщает компилятору о необходимости выполнения определенных математических или логических операций. Эликсир предоставляет МНОГО операторов. Они делятся на следующие категории -

  • Арифметические операторы
  • Операторы сравнения
  • Булевы операторы
  • Разные операторы

Арифметические операторы

В следующей таблице показаны все арифметические операторы, поддерживаемые языком Elixir. Предположим переменнуюA содержит 10 и переменную B держит 20, то -

Показать примеры

Оператор Описание пример
+ Складывает 2 числа. A + B даст 30
- Вычитает второе число из первого. AB даст -10
* Умножает два числа. A * B даст 200
/ Делит первое число от второго. Это приводит числа в число с плавающей запятой и дает результат с плавающей запятой A / B даст 0,5.
div Эта функция используется для получения частного при делении. div (10,20) даст 0
rem Эта функция используется для получения остатка от деления. rem (A, B) даст 10

Операторы сравнения

Операторы сравнения в Elixir в основном общие с операторами большинства других языков. В следующей таблице приведены операторы сравнения в Elixir. Предположим переменнуюA содержит 10 и переменную B держит 20, то -

Показать примеры

Оператор Описание пример
== Проверяет, равно ли значение слева значению справа (тип приводит значения, если они не одного типа). A == B даст false
знак равно Проверяет, не совпадает ли значение слева со значением справа. A! = B даст истину
=== Проверяет, соответствует ли тип значения слева типу значения справа, если да, то проверяет то же самое для значения. A === B даст false
! == То же, что и выше, но проверяет неравенство вместо равенства. A! == B даст истину
> Проверяет, больше ли значение левого операнда, чем значение правого операнда; если да, то условие выполняется. A> B даст false
< Проверяет, меньше ли значение левого операнда, чем значение правого операнда; если да, то условие выполняется. A <B даст истину
> = Проверяет, больше ли значение левого операнда или равно значению правого операнда; если да, то условие выполняется. A> = B даст false
<= Проверяет, меньше ли значение левого операнда или равно значению правого операнда; если да, то условие выполняется. A <= B даст истину

Логические операторы

В Elixir есть 6 логических операторов: and, or, not, &&, || и !. Первые три,and or notявляются строгими логическими операторами, что означает, что они ожидают, что их первый аргумент будет логическим. Не логический аргумент вызовет ошибку. Пока следующие три,&&, || and !не являются строгими, не требуют, чтобы первое значение было строго логическим. Они работают так же, как и их строгие собратья. Предположим переменнуюA верно и изменчиво B держит 20, то -

Показать примеры

Оператор Описание пример
и Проверяет, верны ли оба предоставленных значения, если да, то возвращает значение второй переменной. (Логично и). A и B дадут 20
или же Проверяет, истинно ли любое из указанных значений. Возвращает любое истинное значение. Иначе возвращает false. (Логическое или). A или B даст истину
не Унарный оператор, инвертирующий значение заданного ввода. не А даст ложь
&& Нестрогий and. Работает так же, какand но не ожидает, что первый аргумент будет логическим. B && A даст 20
|| Нестрогий or. Работает так же, какor но не ожидает, что первый аргумент будет логическим. B || А даст истину
! Нестрогий not. Работает так же, какnot но не ожидает, что аргумент будет логическим. ! A даст false

NOTE −и , или , && и || || операторы короткого замыкания. Это означает, что если первый аргументandложно, то второй проверять не будет. И если первый аргументorверно, то второй проверять не будет. Например,

false and raise("An error")  
#This won't raise an error as raise function wont get executed because of short
#circuiting nature of and operator

Побитовые операторы

Побитовые операторы работают с битами и выполняют побитовую операцию. Elixir предоставляет побитовые модули как часть пакетаBitwise, поэтому для их использования необходимо использовать побитовый модуль. Чтобы использовать его, введите в свою оболочку следующую команду -

use Bitwise

Предположим, что A равно 5, а B равно 6 для следующих примеров:

Показать примеры

Оператор Описание пример
&&& Побитовый оператор and копирует бит в результат, если он существует в обоих операндах. A &&& B даст 4
||| Побитовый оператор или копирует бит в результат, если он существует в любом из операндов. А ||| B даст 7
>>> Оператор побитового сдвига вправо сдвигает биты первого операнда вправо на число, указанное во втором операнде. A >>> B даст 0
<<< Оператор побитового сдвига влево сдвигает биты первого операнда влево на число, указанное во втором операнде. A <<< B даст 320
^^^ Побитовый оператор XOR копирует бит в результат, только если он отличается для обоих операндов. A ^^^ B даст 3
~~~ Унарный побитовый не инвертирует биты данного числа. ~~~ А даст -6

Разные операторы

Помимо перечисленных выше операторов, Elixir также предоставляет ряд других операторов, например Concatenation Operator, Match Operator, Pin Operator, Pipe Operator, String Match Operator, Code Point Operator, Capture Operator, Ternary Operator что делает его довольно мощным языком.

Показать примеры

Сопоставление с образцом - это метод, который Elixir наследует от Erlang. Это очень мощный метод, который позволяет нам извлекать более простые подструктуры из сложных структур данных, таких как списки, кортежи, карты и т. Д.

Матч состоит из двух основных частей: left и rightбоковая сторона. Правая сторона - это структура данных любого типа. Левая сторона пытается сопоставить структуру данных с правой стороны и связать любые переменные слева с соответствующей подструктурой справа. Если совпадение не найдено, оператор выдает ошибку.

Самое простое совпадение - это одиночная переменная слева и любая структура данных справа. This variable will match anything. Например,

x = 12
x = "Hello"
IO.puts(x)

Вы можете разместить переменные внутри структуры, чтобы вы могли захватить подструктуру. Например,

[var_1, _unused_var, var_2] = [{"First variable"}, 25, "Second variable" ]
IO.puts(var_1)
IO.puts(var_2)

Это сохранит значения, {"First variable"}в var_1 и"Second variable"в var_2 . Также есть специальный_ переменная (или переменные с префиксом '_'), которая работает точно так же, как другие переменные, но сообщает elixir, "Make sure something is here, but I don't care exactly what it is.". В предыдущем примере одной из таких переменных была _unused_var .

Используя эту технику, мы можем сопоставить более сложные шаблоны. Заexample если вы хотите развернуть и получить число в кортеже, который находится внутри списка, который сам находится в списке, вы можете использовать следующую команду -

[_, [_, {a}]] = ["Random string", [:an_atom, {24}]]
IO.puts(a)

Вышеупомянутая программа генерирует следующий результат -

24

Это свяжет a до 24. Остальные значения игнорируются, поскольку мы используем '_'.

При сопоставлении с образцом, если мы используем переменную в right, используется его значение. Если вы хотите использовать значение переменной слева, вам нужно использовать оператор pin.

Например, если у вас есть переменная «a», имеющая значение 25, и вы хотите сопоставить ее с другой переменной «b», имеющей значение 25, тогда вам необходимо ввести -

a = 25
b = 25
^a = b

Последняя строка соответствует текущему значению aвместо того, чтобы присваивать его значению b. Если у нас есть несовпадающий набор левой и правой стороны, оператор сопоставления вызывает ошибку. Например, если мы попытаемся сопоставить кортеж со списком или список размера 2 со списком размера 3, отобразится ошибка.

Структуры принятия решений требуют, чтобы программист указал одно или несколько условий, которые должны быть оценены или протестированы программой, а также оператор или операторы, которые должны быть выполнены, если условие определено как выполняемое. true, и, необязательно, другие операторы, которые должны быть выполнены, если условие определено как false.

Ниже приводится общий из типичной структуры принятия решений, встречающейся в большинстве языков программирования.

Elixir, как и многие другие языки программирования, предоставляет условные конструкции if / else. Он также имеетcondутверждение, которое вызывает первое найденное истинное значение. Case - это еще один оператор потока управления, который использует сопоставление с образцом для управления потоком программы. Давайте посмотрим на них подробнее.

Elixir предоставляет следующие типы заявлений о принятии решений. Щелкните следующие ссылки, чтобы проверить их детали.

Sr.No. Заявление и описание
1 если заявление

Оператор if состоит из логического выражения, за которым следует do, один или несколько исполняемых операторов и, наконец, endключевое слово. Код в инструкции if выполняется, только если логическое условие истинно.

2 оператор if..else

За оператором if может следовать необязательный оператор else (в блоке do..end), который выполняется, когда логическое выражение ложно.

3 если заявление

Оператор if имеет то же тело, что и оператор if. Код внутри оператора if выполняется только тогда, когда указанное условие ложно.

4 if..else заявление

Оператор if..else имеет то же тело, что и оператор if..else. Код внутри оператора if выполняется только тогда, когда указанное условие ложно.

5 cond

Оператор cond используется там, где мы хотим выполнить код на основе нескольких условий. Это похоже на конструкцию if ... else if ... .else в нескольких других языках программирования.

6 дело

Оператор case можно рассматривать как замену инструкции switch в императивных языках. Case принимает переменную / литерал и применяет к нему сопоставление с образцом с разными регистрами. Если какой-либо регистр совпадает, Elixir выполняет код, связанный с этим случаем, и выходит из оператора case.

Строки в Elixir заключаются в двойные кавычки и кодируются в UTF-8. В отличие от C и C ++, где строки по умолчанию имеют кодировку ASCII и возможны только 256 различных символов, UTF-8 состоит из 1112 064 кодовых точек. Это означает, что кодировка UTF-8 состоит из множества различных возможных символов. Поскольку строки используют utf-8, мы также можем использовать такие символы, как: ö, ł и т. Д.

Создать строку

Чтобы создать строковую переменную, просто назначьте строку переменной -

str = "Hello world"

Чтобы распечатать это на своей консоли, просто вызовите IO.puts функцию и передайте ей переменную str -

str = str = "Hello world" 
IO.puts(str)

Вышеупомянутая программа генерирует следующий результат -

Hello World

Пустые строки

Вы можете создать пустую строку, используя строковый литерал, "". Например,

a = ""
if String.length(a) === 0 do
   IO.puts("a is an empty string")
end

Приведенная выше программа дает следующий результат.

a is an empty string

Строчная интерполяция

Интерполяция строк - это способ создания нового значения String из смеси констант, переменных, литералов и выражений путем включения их значений в строковый литерал. Elixir поддерживает строковую интерполяцию, чтобы использовать переменную в строке, при ее записи оберните ее фигурными скобками и добавьте фигурные скобки перед ними.'#' подписать.

Например,

x = "Apocalypse" 
y = "X-men #{x}"
IO.puts(y)

Это примет значение x и подставит его в y. Приведенный выше код сгенерирует следующий результат -

X-men Apocalypse

Конкатенация строк

Мы уже видели использование конкатенации строк в предыдущих главах. Оператор '<>' используется для объединения строк в Elixir. Чтобы объединить 2 строки,

x = "Dark"
y = "Knight"
z = x <> " " <> y
IO.puts(z)

Приведенный выше код генерирует следующий результат -

Dark Knight

Длина строки

Чтобы получить длину строки, мы используем String.lengthфункция. Передайте строку в качестве параметра, и она покажет вам ее размер. Например,

IO.puts(String.length("Hello"))

При запуске вышеуказанной программы он дает следующий результат -

5

Переворачивание строки

Чтобы перевернуть строку, передайте ее функции String.reverse. Например,

IO.puts(String.reverse("Elixir"))

Вышеупомянутая программа генерирует следующий результат -

rixilE

Сравнение строк

Чтобы сравнить 2 строки, мы можем использовать операторы == или ===. Например,

var_1 = "Hello world"
var_2 = "Hello Elixir"
if var_1 === var_2 do
   IO.puts("#{var_1} and #{var_2} are the same")
else
   IO.puts("#{var_1} and #{var_2} are not the same")
end

Вышеупомянутая программа генерирует следующий результат -

Hello world and Hello elixir are not the same.

Соответствие строк

Мы уже видели использование оператора сопоставления строки = ~. Чтобы проверить, соответствует ли строка регулярному выражению, мы также можем использовать оператор сопоставления строк или String.match? функция. Например,

IO.puts(String.match?("foo", ~r/foo/))
IO.puts(String.match?("bar", ~r/foo/))

Вышеупомянутая программа генерирует следующий результат -

true 
false

Этого же можно добиться с помощью оператора = ~. Например,

IO.puts("foo" =~ ~r/foo/)

Вышеупомянутая программа генерирует следующий результат -

true

Строковые функции

Elixir поддерживает большое количество функций, связанных со строками, некоторые из наиболее часто используемых функций перечислены в следующей таблице.

Sr.No. Функция и ее назначение
1

at(string, position)

Возвращает графему в позиции заданной строки utf8. Если позиция больше длины строки, возвращается ноль

2

capitalize(string)

Преобразует первый символ в данной строке в верхний регистр, а остаток в нижний регистр

3

contains?(string, contents)

Проверяет, содержит ли строка какое-либо из заданного содержимого

4

downcase(string)

Преобразует все символы в данной строке в нижний регистр

5

ends_with?(string, suffixes)

Возвращает истину, если строка заканчивается любым из указанных суффиксов

6

first(string)

Возвращает первую графему из строки utf8, nil, если строка пуста

7

last(string)

Возвращает последнюю графему из строки utf8, nil, если строка пуста

8

replace(subject, pattern, replacement, options \\ [])

Возвращает новую строку, созданную заменой вхождений шаблона в теме с заменой

9

slice(string, start, len)

Возвращает подстроку, начинающуюся с начала смещения, и длиной len

10

split(string)

Делит строку на подстроки при каждом появлении пробелов Unicode, игнорируя начальные и конечные пробелы. Группы пробелов рассматриваются как одно вхождение. Деления не происходят в неразрывных пробелах

11

upcase(string)

Преобразует все символы в данной строке в верхний регистр

Двоичные файлы

Бинарный файл - это просто последовательность байтов. Двоичные файлы определяются с помощью<< >>. Например:

<< 0, 1, 2, 3 >>

Конечно, эти байты можно организовать любым способом, даже в последовательности, которая не делает их действительной строкой. Например,

<< 239, 191, 191 >>

Строки также являются двоичными. И оператор конкатенации строк<> на самом деле является оператором двоичной конкатенации:

IO.puts(<< 0, 1 >> <> << 2, 3 >>)

Приведенный выше код генерирует следующий результат -

<< 0, 1, 2, 3 >>

Обратите внимание на символ ł. Поскольку это кодировка utf-8, это символьное представление занимает 2 байта.

Поскольку каждое число, представленное в двоичном формате, должно быть байтом, когда это значение увеличивается с 255, оно обрезается. Чтобы предотвратить это, мы используем модификатор размера, чтобы указать, сколько битов мы хотим, чтобы это число занимало. Например -

IO.puts(<< 256 >>) # truncated, it'll print << 0 >>
IO.puts(<< 256 :: size(16) >>) #Takes 16 bits/2 bytes, will print << 1, 0 >>

Вышеупомянутая программа сгенерирует следующий результат -

<< 0 >>
<< 1, 0 >>

Мы также можем использовать модификатор utf8, если символ является кодовой точкой, тогда он будет создан на выходе; иначе байты -

IO.puts(<< 256 :: utf8 >>)

Вышеупомянутая программа генерирует следующий результат -

Ā

У нас также есть функция под названием is_binaryкоторый проверяет, является ли данная переменная двоичной. Обратите внимание, что двоичными являются только переменные, кратные 8 битам.

Битовые строки

Если мы определим двоичный файл с помощью модификатора размера и передадим ему значение, не кратное 8, мы получим строку битов вместо двоичного файла. Например,

bs = << 1 :: size(1) >>
IO.puts(bs)
IO.puts(is_binary(bs))
IO.puts(is_bitstring(bs))

Вышеупомянутая программа генерирует следующий результат -

<< 1::size(1) >>
false
true

Это означает, что переменная bsне двоичный файл, а скорее битовая строка. Мы также можем сказать, что двоичный файл - это строка битов, в которой количество битов делится на 8. Сопоставление с образцом работает как с двоичными файлами, так и со строками битов одинаково.

Список символов - это не что иное, как список символов. Рассмотрим следующую программу, чтобы понять то же самое.

IO.puts('Hello')
IO.puts(is_list('Hello'))

Вышеупомянутая программа генерирует следующий результат -

Hello
true

Вместо байтов список символов содержит кодовые точки символов в одинарных кавычках. So while the double-quotes represent a string (i.e. a binary), singlequotes represent a char list (i.e. a list). Обратите внимание, что IEx будет генерировать только кодовые точки в качестве вывода, если какой-либо из символов находится за пределами диапазона ASCII.

Списки символов используются в основном при взаимодействии с Erlang, в частности, в старых библиотеках, которые не принимают двоичные файлы в качестве аргументов. Вы можете преобразовать список символов в строку и обратно с помощью функций to_string (char_list) и to_char_list (строка) -

IO.puts(is_list(to_char_list("hełło")))
IO.puts(is_binary(to_string ('hełło')))

Вышеупомянутая программа генерирует следующий результат -

true
true

NOTE - Функции to_string и to_char_list являются полиморфными, т. е. они могут принимать несколько типов входных данных, таких как атомы, целые числа, и преобразовывать их в строки и списки символов соответственно.

(Связанные) списки

Связанный список - это разнородный список элементов, которые хранятся в разных местах памяти и отслеживаются с помощью ссылок. Связанные списки - это структуры данных, особенно используемые в функциональном программировании.

В Elixir для указания списка значений используются квадратные скобки. Ценности могут быть любого типа -

[1, 2, true, 3]

Когда Elixir видит список печатаемых номеров ASCII, Elixir напечатает его как список символов (буквально список символов). Каждый раз, когда вы видите значение в IEx и не знаете, что это такое, вы можете использоватьi функция для получения информации о нем.

IO.puts([104, 101, 108, 108, 111])

Все перечисленные выше символы в списке можно распечатать. Когда вышеуказанная программа запускается, она дает следующий результат -

hello

Вы также можете определить списки наоборот, используя одинарные кавычки -

IO.puts(is_list('Hello'))

Когда вышеуказанная программа запускается, она дает следующий результат -

true

Имейте в виду, что представления в одинарных и двойных кавычках не эквивалентны в Elixir, поскольку они представлены разными типами.

Длина списка

Чтобы найти длину списка, мы используем функцию длины, как в следующей программе -

IO.puts(length([1, 2, :true, "str"]))

Вышеупомянутая программа генерирует следующий результат -

4

Конкатенация и вычитание

Два списка можно объединить и вычесть с помощью ++ и --операторы. Рассмотрим следующий пример, чтобы понять функции.

IO.puts([1, 2, 3] ++ [4, 5, 6])
IO.puts([1, true, 2, false, 3, true] -- [true, false])

Это даст вам объединенную строку в первом случае и вычтенную строку во втором. Вышеупомянутая программа генерирует следующий результат -

[1, 2, 3, 4, 5, 6]
[1, 2, 3, true]

Начало и конец списка

Голова - это первый элемент списка, а хвост - оставшаяся часть списка. Их можно получить с помощью функцийhd и tl. Давайте присвоим переменной список и получим его заголовок и хвост.

list = [1, 2, 3]
IO.puts(hd(list))
IO.puts(tl(list))

Это даст нам начало и конец списка в качестве вывода. Вышеупомянутая программа генерирует следующий результат -

1
[2, 3]

Note - Получить начало или конец пустого списка - ошибка.

Другие функции списка

Стандартная библиотека Elixir предоставляет множество функций для работы со списками. Мы рассмотрим некоторые из них здесь. Вы можете ознакомиться с остальными здесь Список .

S.no. Название и описание функции
1

delete(list, item)

Удаляет данный элемент из списка. Возвращает список без элемента. Если элемент встречается в списке более одного раза, удаляется только первое вхождение.

2

delete_at(list, index)

Создает новый список, удаляя значение по указанному индексу. Отрицательные индексы указывают смещение от конца списка. Если индекс выходит за допустимые пределы, возвращается исходный список.

3

first(list)

Возвращает первый элемент в списке или ноль, если список пуст.

4

flatten(list)

Сглаживает данный список вложенных списков.

5

insert_at(list, index, value)

Возвращает список со значением, вставленным по указанному индексу. Обратите внимание, что индекс ограничен длиной списка. Отрицательные индексы указывают смещение от конца списка.

6

last(list)

Возвращает последний элемент в списке или ноль, если список пуст.

Кортежи

Кортежи также представляют собой структуры данных, в которых хранится ряд других структур. В отличие от списков, они хранят элементы в непрерывном блоке памяти. Это означает, что доступ к элементу кортежа по индексу или получение размера кортежа - это быстрая операция. Индексы начинаются с нуля.

В Elixir для определения кортежей используются фигурные скобки. Как и списки, кортежи могут содержать любое значение -

{:ok, "hello"}

Длина кортежа

Чтобы получить длину кортежа, используйте tuple_size работают как в следующей программе -

IO.puts(tuple_size({:ok, "hello"}))

Вышеупомянутая программа генерирует следующий результат -

2

Добавление значения

Чтобы добавить значение в кортеж, используйте функцию Tuple.append -

tuple = {:ok, "Hello"}
Tuple.append(tuple, :world)

Это создаст и вернет новый кортеж: {: ok, "Hello",: world}

Вставка значения

Чтобы вставить значение в заданную позицию, мы можем использовать Tuple.insert_at функция или put_elemфункция. Рассмотрим следующий пример, чтобы понять то же самое -

tuple = {:bar, :baz}
new_tuple_1 = Tuple.insert_at(tuple, 0, :foo)
new_tuple_2 = put_elem(tuple, 1, :foobar)

Заметить, что put_elem и insert_atвернул новые кортежи. Исходный кортеж, хранящийся в переменной кортежа, не был изменен, поскольку типы данных Elixir неизменяемы. Поскольку код на Elixir неизменен, его легче рассуждать, поскольку вам не нужно беспокоиться о том, изменяет ли конкретный код вашу структуру данных на месте.

Кортежи против списков

В чем разница между списками и кортежами?

Списки хранятся в памяти как связанные списки, что означает, что каждый элемент в списке содержит свое значение и указывает на следующий элемент до тех пор, пока не будет достигнут конец списка. Каждую пару значений и указателей мы называем cons-ячейкой. Это означает, что доступ к длине списка является линейной операцией: нам нужно пройти весь список, чтобы определить его размер. Обновление списка происходит быстро, пока мы добавляем элементы.

С другой стороны, кортежи хранятся в памяти непрерывно. Это означает, что получение размера кортежа или доступ к элементу по индексу выполняется быстро. Однако обновление или добавление элементов в кортежи обходится дорого, поскольку требует копирования всего кортежа в памяти.

До сих пор мы не обсуждали какие-либо ассоциативные структуры данных, т. Е. Структуры данных, которые могут связывать определенное значение (или несколько значений) с ключом. В разных языках эти функции называются разными именами, такими как словари, хеши, ассоциативные массивы и т. Д.

В Elixir есть две основные ассоциативные структуры данных: списки ключевых слов и карты. В этой главе мы сосредоточимся на списках ключевых слов.

Во многих языках функционального программирования обычно используется список кортежей из двух элементов в качестве представления ассоциативной структуры данных. В Elixir, когда у нас есть список кортежей и первый элемент кортежа (то есть ключ) является атомом, мы называем его списком ключевых слов. Рассмотрим следующий пример, чтобы понять то же самое -

list = [{:a, 1}, {:b, 2}]

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

list_1 = [{:a, 1}, {:b, 2}]
list_2 = [a: 1, b: 2]
IO.puts(list_1 == list_2)

Вышеупомянутая программа сгенерирует следующий результат -

true

Оба они представляют собой список ключевых слов. Поскольку списки ключевых слов также являются списками, мы можем использовать для них все операции, которые мы использовали для списков.

Чтобы получить значение, связанное с атомом в списке ключевых слов, передайте атом как [] после имени списка -

list = [a: 1, b: 2]
IO.puts(list[:a])

Вышеупомянутая программа генерирует следующий результат -

1

Списки ключевых слов обладают тремя особыми характеристиками:

  • Ключи должны быть атомами.
  • Ключи заказываются, как указано разработчиком.
  • Ключи можно давать более одного раза.

Для управления списками ключевых слов в Elixir есть модуль Keyword . Однако помните, что списки ключевых слов - это просто списки, и поэтому они обеспечивают те же линейные характеристики производительности, что и списки. Чем длиннее список, тем больше времени потребуется на поиск ключа, подсчет количества элементов и т. Д. По этой причине списки ключевых слов используются в Elixir в основном как опции. Если вам нужно хранить много элементов или гарантировать связь с одним ключом максимум с одним значением, вам следует вместо этого использовать карты.

Доступ к ключу

Для доступа к значениям, связанным с данным ключом, мы используем Keyword.getфункция. Он возвращает первое значение, связанное с данным ключом. Чтобы получить все значения, мы используем функцию Keyword.get_values. Например -

kl = [a: 1, a: 2, b: 3] 
IO.puts(Keyword.get(kl, :a)) 
IO.puts(Keyword.get_values(kl))

Вышеупомянутая программа сгенерирует следующий результат -

1
[1, 2]

Вставка ключа

Чтобы добавить новое значение, используйте Keyword.put_new. Если ключ уже существует, его значение остается неизменным -

kl = [a: 1, a: 2, b: 3]
kl_new = Keyword.put_new(kl, :c, 5)
IO.puts(Keyword.get(kl_new, :c))

Когда вышеуказанная программа запускается, она создает новый список ключевых слов с дополнительным ключом c и генерирует следующий результат:

5

Удаление ключа

Если вы хотите удалить все записи для ключа, используйте Keyword.delete; чтобы удалить только первую запись для ключа, используйте Keyword.delete_first.

kl = [a: 1, a: 2, b: 3, c: 0]
kl = Keyword.delete_first(kl, :b)
kl = Keyword.delete(kl, :a)

IO.puts(Keyword.get(kl, :a))
IO.puts(Keyword.get(kl, :b))
IO.puts(Keyword.get(kl, :c))

Это удалит первый b в Списке и все aв списке. Когда вышеуказанная программа запущена, она выдаст следующий результат -

0

Списки ключевых слов - это удобный способ адресации содержимого, хранящегося в списках, по ключу, но под ними Elixir все еще проходит по списку. Это может быть подходящим, если у вас есть другие планы для этого списка, требующие обхода всего его, но это может быть ненужными накладными расходами, если вы планируете использовать ключи в качестве единственного подхода к данным.

Здесь вам на помощь приходят карты. Всякий раз, когда вам нужно хранилище ключей и значений, карты являются структурой данных «перехода» в Elixir.

Создание карты

Карта создается с использованием синтаксиса% {} -

map = %{:a => 1, 2 => :b}

По сравнению со списками ключевых слов мы уже видим два отличия:

  • Карты допускают любое значение в качестве ключа.
  • Ключи карт не упорядочены.

Доступ к ключу

Чтобы получить доступ к значению, связанному с ключом, Карты используют тот же синтаксис, что и списки ключевых слов -

map = %{:a => 1, 2 => :b}
IO.puts(map[:a])
IO.puts(map[2])

Когда вышеуказанная программа запускается, она генерирует следующий результат -

1
b

Вставка ключа

Чтобы вставить ключ в карту, мы используем Dict.put_new функция, которая принимает карту, новый ключ и новое значение в качестве аргументов -

map = %{:a => 1, 2 => :b}
new_map = Dict.put_new(map, :new_val, "value") 
IO.puts(new_map[:new_val])

Будет вставлена ​​пара "ключ-значение" :new_val - "value"на новой карте. Когда вышеуказанная программа запускается, она генерирует следующий результат -

"value"

Обновление значения

Чтобы обновить значение, уже присутствующее на карте, вы можете использовать следующий синтаксис -

map = %{:a => 1, 2 => :b}
new_map = %{ map | a: 25}
IO.puts(new_map[:a])

Когда вышеуказанная программа запускается, она генерирует следующий результат -

25

Соответствие шаблону

В отличие от списков ключевых слов, карты очень полезны при сопоставлении с образцом. Когда карта используется в шаблоне, она всегда будет соответствовать подмножеству заданного значения -

%{:a => a} = %{:a => 1, 2 => :b}
IO.puts(a)

Вышеупомянутая программа генерирует следующий результат -

1

Это будет соответствовать a с участием 1. И, следовательно, он будет генерировать вывод как1.

Как показано выше, карта соответствует, пока ключи в шаблоне существуют в данной карте. Следовательно, пустая карта соответствует всем картам.

Переменные можно использовать при доступе, сопоставлении и добавлении ключей карты -

n = 1
map = %{n => :one}
%{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}

Модуль Map предоставляет API, очень похожий на модуль Keyword, с удобными функциями для управления картами. Вы можете использовать такие функции, какMap.get, Map.delete, чтобы управлять картами.

Карты с ключами Atom

Карты обладают несколькими интересными свойствами. Когда все ключи на карте являются атомами, вы можете использовать синтаксис ключевых слов для удобства -

map = %{:a => 1, 2 => :b} 
IO.puts(map.a)

Еще одно интересное свойство карт заключается в том, что они предоставляют собственный синтаксис для обновления ключей атомов и доступа к ним -

map = %{:a => 1, 2 => :b}
IO.puts(map.a)

Вышеупомянутая программа генерирует следующий результат -

1

Обратите внимание, что для доступа к атомным ключам таким образом он должен существовать, иначе программа не сможет работать.

В Elixir мы группируем несколько функций в модули. В предыдущих главах мы уже использовали различные модули, такие как модуль String, модуль Bitwise, модуль Tuple и т. Д.

Для создания собственных модулей в Эликсире мы используем defmoduleмакрос. Мы используемdef макрос для определения функций в этом модуле -

defmodule Math do
   def sum(a, b) do
      a + b
   end
end

В следующих разделах наши примеры будут увеличиваться в размере, и может быть сложно ввести их все в оболочку. Нам нужно научиться компилировать код Эликсира, а также запускать скрипты Эликсира.

Компиляция

Всегда удобно записывать модули в файлы, чтобы их можно было скомпилировать и использовать повторно. Предположим, у нас есть файл с именем math.ex со следующим содержимым -

defmodule Math do
   def sum(a, b) do
      a + b
   end
end

Мы можем скомпилировать файлы с помощью команды -elixirc :

$ elixirc math.ex

Это создаст файл с именем Elixir.Math.beamсодержащий байт-код для определенного модуля. Если мы начнемiexопять же, наше определение модуля будет доступно (при условии, что iex запущен в том же каталоге, в котором находится файл байт-кода). Например,

IO.puts(Math.sum(1, 2))

Вышеупомянутая программа сгенерирует следующий результат -

3

Скриптовый режим

В дополнение к расширению файла Elixir .ex, Elixir также поддерживает .exsфайлы для скриптов. Elixir обращается с обоими файлами одинаково, разница только в цели..ex файлы предназначены для компиляции, а файлы .exs используются для scripting. При запуске оба расширения компилируются и загружают свои модули в память, хотя только.ex файлы записывают свой байт-код на диск в формате файлов .beam.

Например, если мы хотим запустить Math.sum в том же файле мы можем использовать .exs следующим образом:

Math.exs

defmodule Math do
   def sum(a, b) do
      a + b
   end
end
IO.puts(Math.sum(1, 2))

Мы можем запустить его с помощью команды Elixir -

$ elixir math.exs

Вышеупомянутая программа сгенерирует следующий результат -

3

Файл будет скомпилирован в памяти и выполнен с выводом «3» в качестве результата. Файл с байт-кодом не будет создан.

Вложение модулей

Модули могут быть вложены в Эликсир. Эта особенность языка помогает нам лучше организовать наш код. Для создания вложенных модулей мы используем следующий синтаксис -

defmodule Foo do
   #Foo module code here
   defmodule Bar do
      #Bar module code here
   end
end

В приведенном выше примере будут определены два модуля: Foo и Foo.Bar. Ко второму можно получить доступ какBar внутри Fooпока они находятся в одной лексической области. Если позжеBar Модуль перемещается за пределы определения модуля Foo, на него следует ссылаться по его полному имени (Foo.Bar) или должен быть установлен псевдоним с помощью директивы alias, обсуждаемой в главе о псевдонимах.

Note- В Elixir нет необходимости определять модуль Foo, чтобы определить модуль Foo.Bar, поскольку язык переводит все имена модулей в атомы. Вы можете определять произвольно вложенные модули, не определяя никаких модулей в цепочке. Например, вы можете определитьFoo.Bar.Baz без определения Foo или же Foo.Bar.

Чтобы облегчить повторное использование программного обеспечения, Elixir предоставляет три директивы: alias, require и import. Он также предоставляет макрос, называемый использованием, который кратко описан ниже -

# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar

# Ensure the module is compiled and available (usually for macros)
require Foo

# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo

# Invokes the custom code defined in Foo as an extension point
use Foo

Давайте теперь подробно разберемся с каждой директивой.

псевдоним

Директива alias позволяет вам устанавливать псевдонимы для любого заданного имени модуля. Например, если вы хотите указать псевдоним'Str' в модуль String вы можете просто написать -

alias String, as: Str
IO.puts(Str.length("Hello"))

Вышеупомянутая программа генерирует следующий результат -

5

Псевдоним дается String модуль как Str. Теперь, когда мы вызываем любую функцию с помощью литерала Str, она фактически ссылается наStringмодуль. Это очень полезно, когда мы используем очень длинные имена модулей и хотим заменить их более короткими в текущей области.

NOTE - Псевдонимы MUST начинать с заглавной буквы.

Псевдонимы действительны только в пределах lexical scope они вызываются. Например, если у вас есть 2 модуля в файле и вы создаете псевдоним внутри одного из модулей, этот псевдоним не будет доступен во втором модуле.

Если вы даете имя встроенного модуля, например String или Tuple, в качестве псевдонима для какого-либо другого модуля, для доступа к встроенному модулю вам нужно будет добавить его с помощью "Elixir.". Например,

alias List, as: String
#Now when we use String we are actually using List.
#To use the string module: 
IO.puts(Elixir.String.length("Hello"))

Когда вышеуказанная программа запускается, она генерирует следующий результат -

5

требовать

Эликсир предоставляет макросы как механизм для метапрограммирования (написания кода, генерирующего код).

Макросы - это фрагменты кода, которые выполняются и расширяются во время компиляции. Это означает, что для использования макроса нам необходимо гарантировать, что его модуль и реализация доступны во время компиляции. Это делается с помощьюrequire директива.

Integer.is_odd(3)

Когда вышеуказанная программа запущена, она выдаст следующий результат -

** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1

В Эликсире Integer.is_odd определяется как macro. Этот макрос можно использовать как охранник. Это означает, что для вызоваInteger.is_odd, нам понадобится модуль Integer.

Использовать require Integer функции и запустите программу, как показано ниже.

require Integer
Integer.is_odd(3)

На этот раз программа запустится и выдаст следующий результат: true.

Как правило, модуль не требуется перед использованием, за исключением случаев, когда мы хотим использовать макросы, доступные в этом модуле. Попытка вызвать макрос, который не был загружен, вызовет ошибку. Обратите внимание, что, как и директива alias, require также имеет лексическую область видимости . Подробнее о макросах мы поговорим в следующей главе.

импорт

Мы используем importдиректива для простого доступа к функциям или макросам из других модулей без использования полного имени. Например, если мы хотим использоватьduplicate функция из модуля List несколько раз, мы можем просто импортировать ее.

import List, only: [duplicate: 2]

В этом случае мы импортируем только дубликат функции (с длиной списка аргументов 2) из ​​List. Хотя:only является необязательным, его использование рекомендуется во избежание импорта всех функций данного модуля внутри пространства имен. :except также может быть предоставлен как опция для импорта всего в модуле, кроме списка функций.

В import директива также поддерживает :macros и :functions быть отдан :only. Например, чтобы импортировать все макросы, пользователь может написать:

import Integer, only: :macros

Обратите внимание, что импорт тоже Lexically scopedточно так же, как директивы require и alias. Также обратите внимание, что'import'ing a module also 'require's it.

использовать

Хотя это и не директива, use это макрос, тесно связанный с requireчто позволяет использовать модуль в текущем контексте. Макрос использования часто используется разработчиками для добавления внешних функций в текущую лексическую область видимости, часто модулей. Давайте разберемся с директивой использования на примере -

defmodule Example do 
   use Feature, option: :value 
end

Использование - это макрос, который преобразует вышеуказанное в -

defmodule Example do
   require Feature
   Feature.__using__(option: :value)
end

В use Module сначала требуется модуль, а затем вызывает __using__макрос в модуле. Elixir обладает большими возможностями метапрограммирования и имеет макросы для генерации кода во время компиляции. Макрос _ _using__ вызывается в приведенном выше примере, и код внедряется в наш локальный контекст. В локальном контексте макрос использования был вызван во время компиляции.

Функция - это набор операторов, организованных вместе для выполнения определенной задачи. Функции в программировании работают в основном как функции в Math. Вы даете функциям некоторый ввод, они генерируют вывод на основе предоставленного ввода.

В Elixir есть 2 типа функций:

Анонимная функция

Функции, определенные с помощью fn..end constructанонимные функции. Эти функции иногда также называют лямбдами. Они используются путем присвоения им имен переменных.

Именованная функция

Функции, определенные с помощью def keywordназываются функциями. Это встроенные функции, представленные в Elixir.

Анонимные функции

Как следует из названия, анонимная функция не имеет имени. Они часто передаются другим функциям. Чтобы определить анонимную функцию в Эликсире, нам понадобитсяfn и endключевые слова. В них мы можем определить любое количество параметров и тел функций, разделенных->. Например,

sum = fn (a, b) -> a + b end
IO.puts(sum.(1, 5))

При запуске вышеуказанной программы она генерирует следующий результат -

6

Обратите внимание, что эти функции не вызываются как названные функции. У нас есть '.'между именем функции и ее аргументами.

Использование оператора захвата

Мы также можем определить эти функции с помощью оператора захвата. Это более простой способ создания функций. Теперь мы определим указанную выше функцию суммы с помощью оператора захвата,

sum = &(&1 + &2) 
IO.puts(sum.(1, 2))

Когда вышеуказанная программа запускается, она генерирует следующий результат -

3

В сокращенной версии наши параметры не называются, но доступны нам как & 1, & 2, & 3 и так далее.

Функции сопоставления с образцом

Сопоставление с образцом не ограничивается только переменными и структурами данных. Мы можем использовать сопоставление с образцом, чтобы сделать наши функции полиморфными. Например, мы объявим функцию, которая может принимать 1 или 2 входа (в кортеже) и выводить их на консоль,

handle_result = fn
   {var1} -> IO.puts("#{var1} found in a tuple!")
   {var_2, var_3} -> IO.puts("#{var_2} and #{var_3} found!")
end
handle_result.({"Hey people"})
handle_result.({"Hello", "World"})

Когда вышеуказанная программа запускается, она дает следующий результат -

Hey people found in a tuple!
Hello and World found!

Именованные функции

Мы можем определять функции с именами, чтобы мы могли легко обращаться к ним позже. Именованные функции определяются в модуле с помощью ключевого слова def. Именованные функции всегда определяются в модуле. Чтобы вызвать именованные функции, нам нужно ссылаться на них, используя их имя модуля.

Ниже приведен синтаксис именованных функций -

def function_name(argument_1, argument_2) do
   #code to be executed when function is called
end

Давайте теперь определим нашу названную функцию sum в модуле Math.

defmodule Math do
   def sum(a, b) do
      a + b
   end
end

IO.puts(Math.sum(5, 6))

При запуске вышеуказанной программы он дает следующий результат -

11

Для однострочных функций существует сокращенная запись для определения этих функций с использованием do:. Например -

defmodule Math do
   def sum(a, b), do: a + b
end
IO.puts(Math.sum(5, 6))

При запуске вышеуказанной программы он дает следующий результат -

11

Частные функции

Elixir дает нам возможность определять частные функции, к которым можно получить доступ из модуля, в котором они определены. Чтобы определить частную функцию, используйтеdefp вместо def. Например,

defmodule Greeter do
   def hello(name), do: phrase <> name
   defp phrase, do: "Hello "
end

Greeter.hello("world")

Когда вышеуказанная программа запускается, она дает следующий результат -

Hello world

Но если мы просто попробуем явно вызвать фразовую функцию, используя Greeter.phrase() функция, это вызовет ошибку.

Аргументы по умолчанию

Если нам нужно значение по умолчанию для аргумента, мы используем argument \\ value синтаксис -

defmodule Greeter do
   def hello(name, country \\ "en") do
      phrase(country) <> name
   end

   defp phrase("en"), do: "Hello, "
   defp phrase("es"), do: "Hola, "
end

Greeter.hello("Ayush", "en")
Greeter.hello("Ayush")
Greeter.hello("Ayush", "es")

Когда вышеуказанная программа запускается, она дает следующий результат -

Hello, Ayush
Hello, Ayush
Hola, Ayush

Рекурсия - это метод, при котором решение проблемы зависит от решений меньших экземпляров одной и той же проблемы. Большинство языков программирования поддерживают рекурсию, позволяя функции вызывать себя в тексте программы.

В идеале рекурсивные функции имеют условие завершения. Это конечное условие, также известное как базовый случай, прекращает повторный ввод функции и добавление вызовов функций в стек. На этом рекурсивный вызов функции прекращается. Давайте рассмотрим следующий пример, чтобы лучше понять рекурсивную функцию.

defmodule Math do
   def fact(res, num) do
   if num === 1 do
      res
   else
      new_res = res * num
      fact(new_res, num-1)
      end
   end
end

IO.puts(Math.fact(1,5))

Когда вышеуказанная программа запускается, она генерирует следующий результат -

120

Итак, в приведенной выше функции Math.fact, мы вычисляем факториал числа. Обратите внимание, что мы вызываем функцию внутри себя. Давайте теперь разберемся, как это работает.

Мы предоставили ему 1 и число, факториал которого мы хотим вычислить. Функция проверяет, равно ли число 1 или нет, и возвращает res, если оно равно 1.(Ending condition). В противном случае он создает переменную new_res и присваивает ей значение предыдущего res * current num. Он возвращает значение, возвращаемое фактом вызова нашей функции (new_res, num-1) . Это повторяется до тех пор, пока мы не получим число 1. Как только это произойдет, мы получим результат.

Давайте рассмотрим другой пример, выводя каждый элемент списка один за другим. Для этого воспользуемсяhd и tl функции списков и сопоставления с образцом в функциях -

a = ["Hey", 100, 452, :true, "People"]
defmodule ListPrint do
   def print([]) do
   end
   def print([head | tail]) do 
      IO.puts(head)
      print(tail)
   end
end

ListPrint.print(a)

Первая функция печати вызывается, когда у нас есть пустой список(ending condition). Если нет, то будет вызвана вторая функция печати, которая разделит список на 2 и назначит первый элемент списка в начало, а оставшуюся часть списка в конец. Затем печатается голова, и мы снова вызываем функцию печати с остальной частью списка, то есть с хвостом. Когда вышеуказанная программа запускается, она дает следующий результат -

Hey
100
452
true
People

Из-за неизменности циклы в Elixir (как и в любом функциональном языке программирования) написаны не так, как императивные языки. Например, в императивном языке, таком как C, вы напишете -

for(i = 0; i < 10; i++) {
   printf("%d", array[i]);
}

В приведенном выше примере мы изменяем как массив, так и переменную i. В Эликсире мутации невозможны. Вместо этого функциональные языки полагаются на рекурсию: функция вызывается рекурсивно до тех пор, пока не будет достигнуто условие, останавливающее продолжение рекурсивного действия. Никакие данные не изменяются в этом процессе.

Давайте теперь напишем простой цикл, используя рекурсию, которая печатает привет n раз.

defmodule Loop do
   def print_multiple_times(msg, n) when n <= 1 do
      IO.puts msg
   end

   def print_multiple_times(msg, n) do
      IO.puts msg
      print_multiple_times(msg, n - 1)
   end
end

Loop.print_multiple_times("Hello", 10)

Когда вышеуказанная программа запускается, она дает следующий результат -

Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello

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

Эликсир предоставляет нам Enum module. Этот модуль используется для большинства итеративных вызовов циклов, поскольку их гораздо проще использовать, чем пытаться найти рекурсивные определения для них. Мы обсудим это в следующей главе. Ваши собственные рекурсивные определения следует использовать только в том случае, если вы не нашли решения с помощью этого модуля. Эти функции оптимизированы для хвостового вызова и работают довольно быстро.

Перечислимый объект - это объект, который можно перечислить. «Перечислимый» означает отсчет членов набора / коллекции / категории один за другим (обычно по порядку, обычно по имени).

Elixir предоставляет концепцию перечисляемых объектов и модуль Enum для работы с ними. Функции в модуле Enum ограничены, как следует из названия, перечислением значений в структурах данных. Примером перечисляемой структуры данных является список, кортеж, карта и т. Д. Модуль Enum предоставляет нам чуть более 100 функций для работы с перечислениями. В этой главе мы обсудим несколько важных функций.

Все эти функции принимают перечислимое число в качестве первого элемента и функцию в качестве второго и работают с ними. Функции описаны ниже.

все?

Когда мы используем all? функция, вся коллекция должна иметь значение true, иначе будет возвращено false. Например, чтобы проверить, все ли элементы в списке являются нечетными числами, тогда.

res = Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end) 
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

false

Это потому, что не все элементы этого списка нечетные.

Любые?

Как следует из названия, эта функция возвращает истину, если какой-либо элемент коллекции оценивается как истина. Например -

res = Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

true

кусок

Эта функция делит нашу коллекцию на небольшие части, размер которых указан во втором аргументе. Например -

res = Enum.chunk([1, 2, 3, 4, 5, 6], 2)
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

[[1, 2], [3, 4], [5, 6]]

каждый

Может потребоваться перебрать коллекцию без создания нового значения, в этом случае мы используем each функция -

Enum.each(["Hello", "Every", "one"], fn(s) -> IO.puts(s) end)

Когда вышеуказанная программа запускается, она дает следующий результат -

Hello
Every
one

карта

Чтобы применить нашу функцию к каждому элементу и создать новую коллекцию, мы используем функцию карты. Это одна из самых полезных конструкций в функциональном программировании, поскольку она достаточно выразительна и коротка. Давайте рассмотрим пример, чтобы понять это. Мы удвоим значения, хранящиеся в списке, и сохраним их в новом списке.res -

res = Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

[4, 10, 6, 12]

уменьшить

В reduceФункция помогает нам сократить число до единственного значения. Для этого мы предоставляем дополнительный аккумулятор (5 в этом примере), который нужно передать нашей функции; если аккумулятор не указан, используется первое значение -

res = Enum.reduce([1, 2, 3, 4], 5, fn(x, accum) -> x + accum end)
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

15

Аккумулятор - это начальное значение, передаваемое в fn. Начиная со второго вызова, значение, возвращенное предыдущим вызовом, передается как аккумулятор. Мы также можем использовать сокращение без аккумулятора -

res = Enum.reduce([1, 2, 3, 4], fn(x, accum) -> x + accum end)
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

10

uniq

Функция uniq удаляет дубликаты из нашей коллекции и возвращает только набор элементов в коллекции. Например -

res = Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
IO.puts(res)

При запуске вышеуказанной программы он дает следующий результат -

[1, 2, 3, 4]

Жадная оценка

Все функции в модуле Enum готовы. Многие функции ожидают перечислимого и возвращают список. Это означает, что при выполнении нескольких операций с Enum каждая операция будет генерировать промежуточный список, пока мы не достигнем результата. Давайте рассмотрим следующий пример, чтобы понять это -

odd? = &(odd? = &(rem(&1, 2) != 0) 
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum 
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

7500000000

В приведенном выше примере есть конвейер операций. Мы начинаем с диапазона, а затем умножаем каждый элемент в диапазоне на 3. Эта первая операция теперь создаст и вернет список из 100_000 элементов. Затем мы сохраняем все нечетные элементы из списка, генерируя новый список, теперь с 50_000 элементами, а затем суммируем все записи.

В |> символ, использованный в приведенном выше фрагменте, - это pipe operator: он просто берет результат выражения в левой части и передает его в качестве первого аргумента в вызов функции в правой части. Это похоже на Unix | оператор. Его цель - выделить поток данных, трансформируемый рядом функций.

Без pipe оператор, код выглядит сложным -

Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))

У нас есть много других функций, однако здесь описано лишь несколько важных.

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

Потоки поддерживают ленивые операции, а не активные операции перечислениями. Коротко,streams are lazy, composable enumerables. Это означает, что потоки не выполняют операции, если в этом нет крайней необходимости. Давайте рассмотрим пример, чтобы понять это -

odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
IO.puts(res)

Когда вышеуказанная программа запускается, она дает следующий результат -

7500000000

В приведенном выше примере 1..100_000 |> Stream.map(&(&1 * 3))возвращает тип данных, фактический поток, который представляет вычисление карты в диапазоне 1..100_000. Это представление еще не оценивалось. Вместо создания промежуточных списков потоки создают серию вычислений, которые вызываются только тогда, когда мы передаем базовый поток модулю Enum. Потоки полезны при работе с большими, возможно, бесконечными коллекциями.

У потоков и перечислений много общих функций. Потоки в основном предоставляют те же функции, которые предоставляет модуль Enum, который генерирует списки в качестве возвращаемых значений после выполнения вычислений над входными перечислимыми. Некоторые из них перечислены в следующей таблице -

Sr.No. Функция и ее описание
1

chunk(enum, n, step, leftover \\ nil)

Выполняет потоковую передачу перечислимого в блоки, содержащие по n элементов каждый, где каждый новый блок запускает элементы шага в перечислимый.

2

concat(enumerables)

Создает поток, который перечисляет каждое перечислимое в перечисляемом.

3

each(enum, fun)

Выполняет заданную функцию для каждого элемента.

4

filter(enum, fun)

Создает поток, который фильтрует элементы в соответствии с заданной функцией при перечислении.

5

map(enum, fun)

Создает поток, который будет применять данную функцию при перечислении.

6

drop(enum, n)

Лениво отбрасывает следующие n элементов из перечисляемого.

Структуры - это расширения, построенные на основе карт, которые обеспечивают проверки во время компиляции и значения по умолчанию.

Определение структур

Для определения структуры используется конструкция defstruct -

defmodule User do
   defstruct name: "John", age: 27
end

Список ключевых слов, используемый с defstruct, определяет, какие поля структура будет иметь вместе со значениями по умолчанию. Структуры принимают имя модуля, в котором они определены. В приведенном выше примере мы определили структуру с именем User. Теперь мы можем создавать пользовательские структуры, используя синтаксис, аналогичный тому, который используется для создания карт -

new_john = %User{})
ayush = %User{name: "Ayush", age: 20}
megan = %User{name: "Megan"})

Приведенный выше код сгенерирует три разные структуры со значениями -

%User{age: 27, name: "John"}
%User{age: 20, name: "Ayush"}
%User{age: 27, name: "Megan"}

Структуры предоставляют гарантии времени компиляции, что только поля (и все они), определенные с помощью defstruct, будут допущены к существованию в структуре. Таким образом, вы не можете определять свои собственные поля после создания структуры в модуле.

Доступ и обновление структур

Когда мы обсуждали карты, мы показали, как мы можем получить доступ и обновить поля карты. Те же методы (и тот же синтаксис) применимы и к структурам. Например, если мы хотим обновить пользователя, которого мы создали в предыдущем примере, тогда -

defmodule User do
   defstruct name: "John", age: 27
end
john = %User{}
#john right now is: %User{age: 27, name: "John"}

#To access name and age of John, 
IO.puts(john.name)
IO.puts(john.age)

Когда вышеуказанная программа запускается, она дает следующий результат -

John
27

Чтобы обновить значение в структуре, мы снова будем использовать ту же процедуру, которую мы использовали в главе о карте,

meg = %{john | name: "Meg"}

Структуры также могут использоваться при сопоставлении с образцом, как для сопоставления значений определенных ключей, так и для обеспечения того, чтобы сопоставимое значение было структурой того же типа, что и сопоставленное значение.

Протоколы - это механизм для достижения полиморфизма в Elixir. Диспетчеризация по протоколу доступна для любого типа данных, если он реализует протокол.

Рассмотрим пример использования протоколов. Мы использовали функцию под названиемto_stringв предыдущих главах для преобразования из других типов в строковый тип. На самом деле это протокол. Он действует в соответствии с введенными данными, не вызывая ошибок. Может показаться, что мы обсуждаем функции сопоставления с образцом, но по мере продвижения дальше оказывается, что все по-другому.

Рассмотрим следующий пример, чтобы лучше понять механизм протокола.

Давайте создадим протокол, который будет отображать, является ли данный вход пустым или нет. Мы будем называть этот протоколblank?.

Определение протокола

Мы можем определить протокол в Elixir следующим образом:

defprotocol Blank do
   def blank?(data)
end

Как видите, нам не нужно определять тело функции. Если вы знакомы с интерфейсами на других языках программирования, вы можете думать о протоколе как о том же самом.

Итак, этот Протокол говорит, что все, что его реализует, должно иметь empty?функция, хотя разработчик должен решать, как функция отреагирует. Определив протокол, давайте разберемся, как добавить пару реализаций.

Реализация протокола

Поскольку мы определили протокол, теперь нам нужно указать ему, как обрабатывать различные входные данные, которые он может получать. Возьмем за основу взятый ранее пример. Мы реализуем пустой протокол для списков, карт и строк. Это покажет, является ли то, что мы передали, пустым или нет.

#Defining the protocol
defprotocol Blank do
   def blank?(data)
end

#Implementing the protocol for lists
defimpl Blank, for: List do
   def blank?([]), do: true
   def blank?(_), do: false
end

#Implementing the protocol for strings
defimpl Blank, for: BitString do
   def blank?(""), do: true
   def blank?(_), do: false
end

#Implementing the protocol for maps
defimpl Blank, for: Map do
   def blank?(map), do: map_size(map) == 0
end

IO.puts(Blank.blank? [])
IO.puts(Blank.blank? [:true, "Hello"])
IO.puts(Blank.blank? "")
IO.puts(Blank.blank? "Hi")

Вы можете реализовать свой протокол для любого количества или нескольких типов, в зависимости от того, что имеет смысл для использования вашего протокола. Это был довольно простой вариант использования протоколов. Когда вышеуказанная программа запускается, она дает следующий результат -

true
false
true
false

Note - Если вы используете это для любых типов, кроме тех, для которых вы определили протокол, это приведет к ошибке.

Файловый ввод-вывод является неотъемлемой частью любого языка программирования, поскольку он позволяет языку взаимодействовать с файлами в файловой системе. В этой главе мы обсудим два модуля - Путь и Файл.

Модуль Path

В pathmodule - это очень маленький модуль, который можно рассматривать как вспомогательный модуль для операций файловой системы. Большинство функций в модуле File ожидают пути в качестве аргументов. Чаще всего это обычные двоичные файлы. Модуль Path предоставляет возможности для работы с такими путями. Использование функций из модуля Path, а не просто манипулирование двоичными файлами предпочтительнее, поскольку модуль Path прозрачно заботится о различных операционных системах. Следует отметить, что Elixir автоматически преобразует косую черту (/) в обратную косую черту (\) в Windows при выполнении файловых операций.

Давайте рассмотрим следующий пример, чтобы лучше понять модуль Path -

IO.puts(Path.join("foo", "bar"))

Когда вышеуказанная программа запускается, она дает следующий результат -

foo/bar

Модуль path предоставляет множество методов. Вы можете ознакомиться с различными методами здесь . Эти методы часто используются, если вы выполняете много операций с файлами.

Файловый модуль

Модуль файла содержит функции, которые позволяют нам открывать файлы как устройства ввода-вывода. По умолчанию файлы открываются в двоичном режиме, что требует от разработчиков использования определенныхIO.binread и IO.binwriteфункции из модуля ввода-вывода. Создадим файл с именемnewfile и напишите в него данные.

{:ok, file} = File.read("newfile", [:write]) 
# Pattern matching to store returned stream
IO.binwrite(file, "This will be written to the file")

Если вы откроете файл, в который мы только что записали, содержимое будет отображаться следующим образом:

This will be written to the file

Давайте теперь поймем, как использовать файловый модуль.

Открытие файла

Чтобы открыть файл, мы можем использовать любую из следующих 2 функций:

{:ok, file} = File.open("newfile")
file = File.open!("newfile")

Давайте теперь поймем разницу между File.open функция и File.open!() функция.

  • В File.openфункция всегда возвращает кортеж. Если файл успешно открыт, он возвращает первое значение в кортеже как:okа второе значение - литерал типа io_device. Если возникла ошибка, он вернет кортеж с первым значением как:error и второе значение как причина.

  • В File.open!() функция с другой стороны вернет io_deviceесли файл успешно открыт, иначе это вызовет ошибку. ПРИМЕЧАНИЕ. Это шаблон, которому следуют все функции файловых модулей, которые мы собираемся обсудить.

Мы также можем указать режимы, в которых мы хотим открыть этот файл. Чтобы открыть файл только для чтения и в режиме кодировки utf-8, мы используем следующий код -

file = File.open!("newfile", [:read, :utf8])

Запись в файл

У нас есть два способа записи в файлы. Давайте посмотрим на первый, используя функцию записи из модуля File.

File.write("newfile", "Hello")

Но это не следует использовать, если вы делаете несколько записей в один и тот же файл. Каждый раз, когда вызывается эта функция, открывается дескриптор файла, и создается новый процесс для записи в файл. Если вы выполняете несколько операций записи в цикле, откройте файл черезFile.openи напишите в него, используя методы модуля ввода-вывода. Давайте рассмотрим пример, чтобы понять то же самое -

#Open the file in read, write and utf8 modes. 
file = File.open!("newfile_2", [:read, :utf8, :write])

#Write to this "io_device" using standard IO functions
IO.puts(file, "Random text")

Вы можете использовать другие методы модуля ввода-вывода, например IO.write и IO.binwrite для записи в файлы, открытые как io_device.

Чтение из файла

У нас есть два способа читать из файлов. Давайте посмотрим на первый, используя функцию чтения из модуля File.

IO.puts(File.read("newfile"))

При запуске этого кода вы должны получить кортеж с первым элементом как :ok а второй - как содержимое newfile

Мы также можем использовать File.read! функция, чтобы просто получить содержимое возвращенных нам файлов.

Закрытие открытого файла

Каждый раз, когда вы открываете файл с помощью функции File.open, после того, как вы закончили его использовать, вы должны закрыть его с помощью File.close функция -

File.close(file)

В Elixir весь код выполняется внутри процессов. Процессы изолированы друг от друга, выполняются одновременно и обмениваются данными посредством передачи сообщений. Не следует путать процессы Elixir с процессами операционной системы. Процессы в Elixir чрезвычайно легкие с точки зрения памяти и ЦП (в отличие от потоков во многих других языках программирования). Из-за этого нередко одновременно работают десятки или даже сотни тысяч процессов.

В этой главе мы узнаем об основных конструкциях для порождения новых процессов, а также об отправке и получении сообщений между различными процессами.

Функция появления

Самый простой способ создать новый процесс - использовать spawnфункция. Вspawnпринимает функцию, которая будет запущена в новом процессе. Например -

pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)

Когда вышеуказанная программа запускается, она дает следующий результат -

false

Возвращаемое значение функции порождения - PID. Это уникальный идентификатор процесса, поэтому, если вы запустите код над своим PID, он будет другим. Как вы можете видеть в этом примере, процесс мертв, когда мы проверяем, жив ли он. Это потому, что процесс завершится, как только он завершит выполнение данной функции.

Как уже упоминалось, все коды Elixir выполняются внутри процессов. Если вы запустите функцию self, вы увидите PID для вашего текущего сеанса -

pid = self
 
Process.alive?(pid)

Когда вышеуказанная программа запускается, она дает следующий результат -

true

Сообщение передается

Мы можем отправлять сообщения процессу с send и получить их с receive. Давайте передадим сообщение текущему процессу и получим его тем же.

send(self(), {:hello, "Hi people"})

receive do
   {:hello, msg} -> IO.puts(msg)
   {:another_case, msg} -> IO.puts("This one won't match!")
end

Когда вышеуказанная программа запускается, она дает следующий результат -

Hi people

Мы отправили сообщение текущему процессу с помощью функции отправки и передали его PID self. Затем мы обработали входящее сообщение, используяreceive функция.

Когда сообщение отправляется процессу, оно сохраняется в process mailbox. Блок приема проходит через почтовый ящик текущего процесса в поисках сообщения, которое соответствует любому из заданных шаблонов. Блок приема поддерживает охранники и многие пункты, такие как case.

Если в почтовом ящике нет сообщения, соответствующего какому-либо из шаблонов, текущий процесс будет ждать, пока не прибудет соответствующее сообщение. Также можно указать тайм-аут. Например,

receive do
   {:hello, msg}  -> msg
after
   1_000 -> "nothing after 1s"
end

Когда вышеуказанная программа запускается, она дает следующий результат -

nothing after 1s

NOTE - Таймаут 0 может быть задан, если вы уже ожидаете, что сообщение будет в почтовом ящике.

Ссылки

Самая распространенная форма появления в Эликсире - через spawn_linkфункция. Прежде чем взглянуть на пример с spawn_link, давайте разберемся, что происходит, когда процесс выходит из строя.

spawn fn -> raise "oops" end

Когда вышеуказанная программа запускается, она выдает следующую ошибку -

[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
   :erlang.apply/2

Произошла ошибка, но процесс создания все еще продолжается. Это потому, что процессы изолированы. Если мы хотим, чтобы сбой в одном процессе распространялся на другой, нам нужно связать их. Это можно сделать с помощьюspawn_linkфункция. Давайте рассмотрим пример, чтобы понять то же самое -

spawn_link fn -> raise "oops" end

Когда вышеуказанная программа запускается, она выдает следующую ошибку -

** (EXIT from #PID<0.41.0>) an exception was raised:
   ** (RuntimeError) oops
      :erlang.apply/2

Если вы запускаете это в iexshell, то оболочка обрабатывает эту ошибку и не завершает работу. Но если вы запустите, сначала создав файл сценария, а затем используяelixir <file-name>.exs, родительский процесс также будет остановлен из-за этого сбоя.

Процессы и связи играют важную роль при построении отказоустойчивых систем. В приложениях Elixir мы часто связываем наши процессы с супервизорами, которые обнаруживают, когда процесс умирает, и запускают новый процесс вместо него. Это возможно только потому, что процессы изолированы и по умолчанию ничего не разделяют. А поскольку процессы изолированы, сбой в процессе не может привести к сбою или повреждению состояния другого. В то время как другие языки потребуют от нас перехвата / обработки исключений; в Elixir мы вполне можем позволить процессам терпеть неудачу, потому что мы ожидаем, что супервизоры правильно перезапустят наши системы.

состояние

Если вы создаете приложение, которому требуется состояние, например, для сохранения конфигурации приложения, или если вам нужно проанализировать файл и сохранить его в памяти, где бы вы его сохранили? Функциональные возможности процесса Elixir могут пригодиться при выполнении таких задач.

Мы можем писать процессы, которые зацикливаются бесконечно, поддерживают состояние и отправляют и получают сообщения. В качестве примера давайте напишем модуль, который запускает новые процессы, которые работают как хранилище значений ключей в файле с именемkv.exs.

defmodule KV do
   def start_link do
      Task.start_link(fn -> loop(%{}) end)
   end

   defp loop(map) do
      receive do
         {:get, key, caller} ->
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} ->
         loop(Map.put(map, key, value))
      end
   end
end

Обратите внимание, что start_link функция запускает новый процесс, который запускает loopфункция, начиная с пустой карты. ВloopЗатем функция ожидает сообщений и выполняет соответствующее действие для каждого сообщения. В случае:getmessage, он отправляет сообщение обратно вызывающему абоненту и снова вызывает цикл, чтобы дождаться нового сообщения. В то время как:put сообщение действительно вызывает loop с новой версией карты с сохраненным заданным ключом и значением.

Давайте теперь запустим следующее -

iex kv.exs

Теперь ты должен быть в своем iexоболочка. Чтобы протестировать наш модуль, попробуйте следующее -

{:ok, pid} = KV.start_link

# pid now has the pid of our new process that is being 
# used to get and store key value pairs 

# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}

# Ask for the key :hello
send pid, {:get, :hello, self()}

# Print all the received messages on the current process.
flush()

Когда вышеуказанная программа запускается, она дает следующий результат -

"Hello"

В этой главе мы собираемся изучить сигилы, механизмы, предоставляемые языком для работы с текстовыми представлениями. Сигилы начинаются с символа тильды (~), за которым следует буква (которая идентифицирует сигилу) и затем разделитель; необязательно, модификаторы могут быть добавлены после последнего разделителя.

Регулярное выражение

Регулярные выражения в Elixir - это сигилы. Мы видели их использование в главе о String. Давайте снова рассмотрим пример, чтобы увидеть, как мы можем использовать регулярное выражение в Elixir.

# A regular expression that matches strings which contain "foo" or
# "bar":
regex = ~r/foo|bar/
IO.puts("foo" =~ regex)
IO.puts("baz" =~ regex)

Когда вышеуказанная программа запускается, она дает следующий результат -

true
false

Сигилы поддерживают 8 различных разделителей -

~r/hello/
~r|hello|
~r"hello"
~r'hello'
~r(hello)
~r[hello]
~r{hello}
~r<hello>

Причина поддержки разных разделителей заключается в том, что разные разделители могут больше подходить для разных сигил. Например, использование круглых скобок для регулярных выражений может сбивать с толку, поскольку они могут смешиваться со скобками внутри регулярного выражения. Однако круглые скобки могут быть полезны для других сигил, как мы увидим в следующем разделе.

Elixir поддерживает регулярные выражения, совместимые с Perl, а также поддерживает модификаторы. Вы можете узнать больше об использовании регулярных выражений здесь .

Строки, списки символов и списки слов

Помимо регулярных выражений, в Elixir есть еще 3 встроенных сигилы. Давайте посмотрим на сигилы.

Струны

Сигил ~ s используется для генерации строк, как и двойные кавычки. Символ ~ s полезен, например, когда строка содержит как двойные, так и одинарные кавычки -

new_string = ~s(this is a string with "double" quotes, not 'single' ones)
IO.puts(new_string)

Эта сигилла генерирует строки. Когда вышеуказанная программа запускается, она дает следующий результат -

"this is a string with \"double\" quotes, not 'single' ones"

Списки символов

Сигил ~ c используется для генерации списков символов -

new_char_list = ~c(this is a char list containing 'single quotes')
IO.puts(new_char_list)

Когда вышеуказанная программа запускается, она дает следующий результат -

this is a char list containing 'single quotes'

Списки слов

Сигил ~ w используется для создания списков слов (слова - это просто обычные строки). Внутри сигилы ~ w слова разделяются пробелами.

new_word_list = ~w(foo bar bat)
IO.puts(new_word_list)

Когда вышеуказанная программа запускается, она дает следующий результат -

foobarbat

Сигил ~ w также принимает c, s и a модификаторы (для списков символов, строк и атомов соответственно), которые определяют тип данных элементов результирующего списка -

new_atom_list = ~w(foo bar bat)a
IO.puts(new_atom_list)

Когда вышеуказанная программа запускается, она дает следующий результат -

[:foo, :bar, :bat]

Интерполяция и побег в сигиллах

Помимо символов в нижнем регистре, Elixir поддерживает символы в верхнем регистре для работы с экранирующими символами и интерполяцией. Хотя и ~ s, и ~ S будут возвращать строки, первый допускает escape-коды и интерполяцию, а второй - нет. Давайте рассмотрим пример, чтобы понять это -

~s(String with escape codes \x26 #{"inter" <> "polation"})
# "String with escape codes & interpolation"
~S(String without escape codes \x26 without #{interpolation})
# "String without escape codes \\x26 without \#{interpolation}"

Пользовательские сигилы

Мы можем легко создать свои собственные сигилы. В этом примере мы создадим сигил для преобразования строки в верхний регистр.

defmodule CustomSigil do
   def sigil_u(string, []), do: String.upcase(string)
end

import CustomSigil

IO.puts(~u/tutorials point/)

Когда мы запускаем приведенный выше код, он дает следующий результат:

TUTORIALS POINT

Сначала мы определяем модуль под названием CustomSigil, и внутри этого модуля мы создали функцию под названием sigil_u. Поскольку в существующем пространстве сигил нет существующей сигилы ~ u, мы будем ее использовать. _U указывает, что мы хотим использовать u в качестве символа после тильды. Определение функции должно принимать два аргумента: вход и список.

Понимание списков - это синтаксический сахар для перебора перечислимых элементов в Elixir. В этой главе мы будем использовать пояснения для итерации и генерации.

Основы

Когда мы посмотрели на модуль Enum в главе о перечислениях, мы натолкнулись на функцию карты.

Enum.map(1..3, &(&1 * 2))

В этом примере мы передадим функцию в качестве второго аргумента. Каждый элемент в диапазоне будет передан в функцию, а затем будет возвращен новый список, содержащий новые значения.

Отображение, фильтрация и преобразование - очень распространенные действия в Elixir, поэтому есть несколько другой способ достижения того же результата, что и в предыдущем примере -

for n <- 1..3, do: n * 2

Когда мы запускаем приведенный выше код, он дает следующий результат:

[2, 4, 6]

Второй пример - это понимание, и, как вы, вероятно, видите, это просто синтаксический сахар для того, чего вы также можете достичь, если используете Enum.mapфункция. Однако с точки зрения производительности нет реальных преимуществ использования понимания перед функцией из модуля Enum.

Понимания не ограничиваются списками, но могут использоваться со всеми перечисляемыми.

Фильтр

Вы можете рассматривать фильтры как своего рода охрану для понимания. Когда возвращается отфильтрованное значениеfalse или же nilон исключен из окончательного списка. Давайте переберем диапазон и будем беспокоиться только о четных числах. Мы будем использоватьis_even из модуля Integer, чтобы проверить, четное значение или нет.

import Integer
IO.puts(for x <- 1..10, is_even(x), do: x)

Когда приведенный выше код запускается, он дает следующий результат:

[2, 4, 6, 8, 10]

Мы также можем использовать несколько фильтров в одном понимании. Добавьте другой фильтр, который вы хотите, послеis_even фильтр через запятую.

: в вариант

В приведенных выше примерах все понимания возвращали списки в качестве своего результата. Однако результат понимания может быть вставлен в различные структуры данных путем передачи:into вариант к пониманию.

Например, bitstring генератор можно использовать с параметром: into, чтобы легко удалить все пробелы в строке -

IO.puts(for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>)

Когда приведенный выше код запускается, он дает следующий результат:

helloworld

Приведенный выше код удаляет все пробелы из строки, используя c != ?\s filter, а затем с помощью параметра: into помещает все возвращенные символы в строку.

Elixir - это язык с динамической типизацией, поэтому все типы в Elixir определяются средой выполнения. Тем не менее, Elixir поставляется со спецификациями типов, которые используются для обозначенияdeclaring custom data types and declaring typed function signatures (specifications).

Функциональные характеристики (спецификации)

По умолчанию Elixir предоставляет некоторые базовые типы, такие как integer или pid, а также сложные типы: например, roundфункция, которая округляет число с плавающей запятой до ближайшего целого числа, принимает число в качестве аргумента (целое число или число с плавающей запятой) и возвращает целое число. В соответствующей документации круглая подпись написана как -

round(number) :: integer

Приведенное выше описание подразумевает, что функция слева принимает в качестве аргумента то, что указано в скобках, и возвращает то, что находится справа от ::, т.е. целое число. Спецификации функций написаны с@specдиректива, размещенная прямо перед определением функции. Круглая функция может быть записана как -

@spec round(number) :: integer
def round(number), do: # Function implementation
...

Спецификации типов также поддерживают сложные типы, например, если вы хотите вернуть список целых чисел, вы можете использовать [Integer]

Пользовательские типы

Хотя Elixir предоставляет множество полезных встроенных типов, при необходимости удобно определять собственные типы. Это можно сделать при определении модулей с помощью директивы @type. Давайте рассмотрим пример, чтобы понять то же самое -

defmodule FunnyCalculator do
   @type number_with_joke :: {number, String.t}

   @spec add(number, number) :: number_with_joke
   def add(x, y), do: {x + y, "You need a calculator to do that?"}

   @spec multiply(number, number) :: number_with_joke
   def multiply(x, y), do: {x * y, "It is like addition on steroids."}
end

{result, comment} = FunnyCalculator.add(10, 20)
IO.puts(result)
IO.puts(comment)

Когда вышеуказанная программа запускается, она дает следующий результат -

30
You need a calculator to do that?

NOTE - Пользовательские типы, определенные с помощью @type, экспортируются и доступны за пределами модуля, в котором они определены. Если вы хотите сохранить частный пользовательский тип, вы можете использовать @typep директива вместо @type.

Поведение в Elixir (и Erlang) - это способ отделить и абстрагировать общую часть компонента (которая становится модулем поведения) от конкретной части (которая становится модулем обратного вызова). Поведение дает возможность -

  • Определите набор функций, которые должны быть реализованы модулем.
  • Убедитесь, что модуль реализует все функции в этом наборе.

При необходимости вы можете думать о поведении как о интерфейсах в объектно-ориентированных языках, таких как Java: набор сигнатур функций, которые модуль должен реализовать.

Определение поведения

Давайте рассмотрим пример, чтобы создать собственное поведение, а затем использовать это общее поведение для создания модуля. Мы определим поведение, при котором люди приветствуют и прощают на разных языках.

defmodule GreetBehaviour do
   @callback say_hello(name :: string) :: nil
   @callback say_bye(name :: string) :: nil
end

В @callbackДиректива используется для перечисления функций, которые необходимо определить модулям. Также указывается номер. аргументов, их типа и возвращаемых значений.

Принятие поведения

Мы успешно определили поведение. Теперь мы примем и реализуем его в нескольких модулях. Давайте создадим два модуля, реализующих это поведение на английском и испанском языках.

defmodule GreetBehaviour do
   @callback say_hello(name :: string) :: nil
   @callback say_bye(name :: string) :: nil
end

defmodule EnglishGreet do
   @behaviour GreetBehaviour
   def say_hello(name), do: IO.puts("Hello " <> name)
   def say_bye(name), do: IO.puts("Goodbye, " <> name)
end

defmodule SpanishGreet do
   @behaviour GreetBehaviour
   def say_hello(name), do: IO.puts("Hola " <> name)
   def say_bye(name), do: IO.puts("Adios " <> name)
end

EnglishGreet.say_hello("Ayush")
EnglishGreet.say_bye("Ayush")
SpanishGreet.say_hello("Ayush")
SpanishGreet.say_bye("Ayush")

Когда вышеуказанная программа запускается, она дает следующий результат -

Hello Ayush
Goodbye, Ayush
Hola Ayush
Adios Ayush

Как вы уже видели, мы принимаем поведение, используя @behaviourдиректива в модуле. Мы должны определить все функции, реализованные в поведении для всех дочерних модулей. Это можно примерно считать эквивалентом интерфейсов на языках ООП.

В Elixir есть три механизма ошибок: ошибки, выбросы и выходы. Разберем каждый механизм подробно.

ошибка

Ошибки (или исключения) используются, когда в коде происходят исключительные вещи. Пример ошибки можно получить, попытавшись добавить число в строку -

IO.puts(1 + "Hello")

Когда вышеуказанная программа запускается, она выдает следующую ошибку -

** (ArithmeticError) bad argument in arithmetic expression
   :erlang.+(1, "Hello")

Это была встроенная ошибка образца.

Возникновение ошибок

Мы можем raiseошибки с использованием функций повышения. Давайте рассмотрим пример, чтобы понять то же самое -

#Runtime Error with just a message
raise "oops"  # ** (RuntimeError) oops

Другие ошибки могут быть вызваны с помощью команды raise / 2, передающей имя ошибки и список аргументов ключевых слов.

#Other error type with a message
raise ArgumentError, message: "invalid argument foo"

Вы также можете определять свои собственные ошибки и поднимать их. Рассмотрим следующий пример -

defmodule MyError do
   defexception message: "default message"
end

raise MyError  # Raises error with default message
raise MyError, message: "custom message"  # Raises error with custom message

Спасение ошибок

Мы не хотим, чтобы наши программы внезапно завершали работу, а, скорее, нужно осторожно обрабатывать ошибки. Для этого мы используем обработку ошибок. Мыrescue ошибки с использованием try/rescueпостроить. Давайте рассмотрим следующий пример, чтобы понять то же самое -

err = try do
   raise "oops"
rescue
   e in RuntimeError -> e
end

IO.puts(err.message)

Когда вышеуказанная программа запускается, она дает следующий результат -

oops

Мы обработали ошибки в операторе восстановления, используя сопоставление с образцом. Если мы не используем ошибку и просто хотим использовать ее для идентификации, мы также можем использовать форму -

err = try do
   1 + "Hello"
rescue
   RuntimeError -> "You've got a runtime error!"
   ArithmeticError -> "You've got a Argument error!"
end

IO.puts(err)

При запуске вышеуказанной программы он дает следующий результат -

You've got a Argument error!

NOTE- Большинство функций в стандартной библиотеке Elixir реализованы дважды, один раз возвращая кортежи, а другой - ошибки. Например,File.read и File.read!функции. Первый вернул кортеж, если файл был успешно прочитан, и если произошла ошибка, этот кортеж использовался, чтобы указать причину ошибки. Второй вызывал ошибку, если была обнаружена ошибка.

Если мы используем подход с использованием первой функции, тогда нам нужно использовать прецедент для сопоставления с шаблоном ошибки и принять меры в соответствии с этим. Во втором случае мы используем подход try rescue для кода, подверженного ошибкам, и обрабатываем ошибки соответственно.

Броски

В Elixir значение может быть выброшено, а затем обнаружено. Throw и Catch зарезервированы для ситуаций, когда невозможно получить значение, кроме случаев использования throw и catch.

На практике такие экземпляры встречаются довольно редко, за исключением случаев взаимодействия с библиотеками. Например, давайте теперь предположим, что модуль Enum не предоставляет никакого API для поиска значения и что нам нужно найти первое кратное 13 в списке чисел -

val = try do
   Enum.each 20..100, fn(x) ->
      if rem(x, 13) == 0, do: throw(x)
   end
   "Got nothing"
catch
   x -> "Got #{x}"
end

IO.puts(val)

Когда вышеуказанная программа запускается, она дает следующий результат -

Got 26

Выход

Когда процесс умирает по «естественным причинам» (например, из-за необработанных исключений), он отправляет сигнал выхода. Процесс также может умереть, явно посылая сигнал выхода. Давайте рассмотрим следующий пример -

spawn_link fn -> exit(1) end

В приведенном выше примере связанный процесс завершился отправкой сигнала выхода со значением 1. Обратите внимание, что exit также можно «поймать» с помощью try / catch. Например -

val = try do
   exit "I am exiting"
catch
   :exit, _ -> "not really"
end

IO.puts(val)

Когда вышеуказанная программа запускается, она дает следующий результат -

not really

После

Иногда необходимо обеспечить очистку ресурса после некоторого действия, которое потенциально может вызвать ошибку. Конструкция try / after позволяет вам это делать. Например, мы можем открыть файл и использовать предложение after, чтобы закрыть его, даже если что-то пойдет не так.

{:ok, file} = File.open "sample", [:utf8, :write]
try do
   IO.write file, "olá"
   raise "oops, something went wrong"
after
   File.close(file)
end

Когда мы запустим эту программу, она выдаст нам ошибку. Ноafter оператор гарантирует, что дескриптор файла будет закрыт при любом таком событии.

Макросы - одна из самых продвинутых и мощных функций Elixir. Как и все дополнительные функции любого языка, макросы следует использовать с осторожностью. Они позволяют выполнять мощные преобразования кода во время компиляции. Теперь мы вкратце разберемся, что такое макросы и как их использовать.

Цитата

Прежде чем мы начнем говорить о макросах, давайте сначала посмотрим на внутреннее устройство Elixir. Программа на Эликсире может быть представлена ​​собственными структурами данных. Строительный блок программы на Эликсире - это кортеж из трех элементов. Например, сумма вызова функции (1, 2, 3) внутренне представлена ​​как -

{:sum, [], [1, 2, 3]}

Первый элемент - это имя функции, второй - список ключевых слов, содержащий метаданные, а третий - список аргументов. Вы можете получить это как результат в оболочке iex, если напишете следующее:

quote do: sum(1, 2, 3)

Операторы также представлены такими кортежами. Переменные также представлены с использованием таких триплетов, за исключением того, что последний элемент является атомом, а не списком. При цитировании более сложных выражений мы видим, что код представлен такими кортежами, которые часто вложены друг в друга в структуру, напоминающую дерево. Многие языки назвали бы такие представленияAbstract Syntax Tree (AST). Эликсир называет эти цитируемые выражения.

Отменить цитату

Теперь, когда мы можем получить внутреннюю структуру нашего кода, как нам ее изменить? Чтобы ввести новый код или значения, мы используемunquote. Когда мы отменяем кавычки для выражения, оно будет оценено и введено в AST. Давайте рассмотрим пример (в оболочке iex), чтобы понять концепцию -

num = 25

quote do: sum(15, num)

quote do: sum(15, unquote(num))

Когда вышеуказанная программа запускается, она дает следующий результат -

{:sum, [], [15, {:num, [], Elixir}]}
{:sum, [], [15, 25]}

В примере для выражения кавычки оно не заменяет автоматически num на 25. Нам нужно убрать кавычки с этой переменной, если мы хотим изменить AST.

Макросы

Итак, теперь, когда мы знакомы с цитированием и отменой цитирования, мы можем изучить метапрограммирование в Elixir с помощью макросов.

Проще говоря, макросы - это специальные функции, предназначенные для возврата выражения в кавычках, которое будет вставлено в код нашего приложения. Представьте, что макрос заменяется выражением в кавычках, а не вызывается как функция. Благодаря макросам у нас есть все необходимое для расширения Elixir и динамического добавления кода в наши приложения.

Реализуем разве как макрос. Мы начнем с определения макроса с помощьюdefmacroмакрос. Помните, что наш макрос должен возвращать выражение в кавычках.

defmodule OurMacro do
   defmacro unless(expr, do: block) do
      quote do
         if !unquote(expr), do: unquote(block)
      end
   end
end

require OurMacro

OurMacro.unless true, do: IO.puts "True Expression"

OurMacro.unless false, do: IO.puts "False expression"

Когда вышеуказанная программа запускается, она дает следующий результат -

False expression

То , что происходит здесь наш код заменяется приведённым кодом возвращаемого исключения случаев , когда макрос. Мы не заключили в кавычки выражение, чтобы оценить его в текущем контексте, а также не заключили в кавычки блок do, чтобы выполнить его в своем контексте. Этот пример показывает нам метапрограммирование с использованием макросов в эликсире.

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

Elixir обеспечивает отличную совместимость с библиотеками Erlang. Давайте кратко обсудим несколько библиотек.

Двоичный модуль

Встроенный модуль Elixir String обрабатывает двоичные файлы в кодировке UTF-8. Двоичный модуль полезен, когда вы имеете дело с двоичными данными, которые не обязательно закодированы в UTF-8. Давайте рассмотрим пример, чтобы лучше понять двоичный модуль -

# UTF-8
IO.puts(String.to_char_list("Ø"))

# binary
IO.puts(:binary.bin_to_list "Ø")

Когда вышеуказанная программа запускается, она дает следующий результат -

[216]
[195, 152]

Приведенный выше пример показывает разницу; модуль String возвращает кодовые точки UTF-8, а: binary работает с байтами необработанных данных.

Крипто-модуль

Модуль шифрования содержит функции хеширования, цифровые подписи, шифрование и многое другое. Этот модуль не является частью стандартной библиотеки Erlang, но включен в дистрибутив Erlang. Это означает, что вы должны указывать: crypto в списке приложений вашего проекта всякий раз, когда вы его используете. Давайте посмотрим на пример использования модуля криптографии -

IO.puts(Base.encode16(:crypto.hash(:sha256, "Elixir")))

Когда вышеуказанная программа запускается, она дает следующий результат -

3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB

Модуль Digraph

Модуль орграфов содержит функции для работы с ориентированными графами, построенными из вершин и ребер. После построения графа используемые алгоритмы помогут найти, например, кратчайший путь между двумя вершинами или петли в графе. Обратите внимание, что функцииin :digraph косвенно изменить структуру графа в качестве побочного эффекта, возвращая добавленные вершины или ребра.

digraph = :digraph.new()
coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
[v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
:digraph.add_edge(digraph, v0, v1)
:digraph.add_edge(digraph, v1, v2)
for point <- :digraph.get_short_path(digraph, v0, v2) do 
   {x, y} = point
   IO.puts("#{x}, #{y}")
end

Когда вышеуказанная программа запускается, она дает следующий результат -

0.0, 0.0
1.0, 0.0
1.0, 1.0

Математический модуль

Математический модуль содержит общие математические операции, охватывающие тригонометрические, экспоненциальные и логарифмические функции. Давайте рассмотрим следующий пример, чтобы понять, как работает модуль Math -

# Value of pi
IO.puts(:math.pi())

# Logarithm
IO.puts(:math.log(7.694785265142018e23))

# Exponentiation
IO.puts(:math.exp(55.0))

#...

Когда вышеуказанная программа запускается, она дает следующий результат -

3.141592653589793
55.0
7.694785265142018e23

Модуль очереди

Очередь - это структура данных, которая эффективно реализует (двусторонние) очереди FIFO (first-in first-out). В следующем примере показано, как работает модуль очереди -

q = :queue.new
q = :queue.in("A", q)
q = :queue.in("B", q)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)

Когда вышеуказанная программа запускается, она дает следующий результат -

A
B