Перейти до вмісту

Erlang

Матеріал з Вікіпедії — вільної енциклопедії.
Erlang
Парадигмамультипарадигмальна: паралельна, функційна
Дата появи1987
ТворціДжо Армстронг (програміст)
РозробникEricsson
Останній реліз25 (18 червня, 2022; 2 роки тому (2022-06-18))
Система типізаціїдинамічна типізація,сильна типізація
Під впливом відProlog, Smalltalk Lisp, PLEX
Вплинула наF#, Clojure, Rust, Scala, Opa, Reia,Akka, Dart, Elixir, Oz,Go
Мова реалізаціїErlang[1]
Операційна системабагатоплатформова
ЛіцензіяApache 2.0
Звичайні розширення файлів.erl, .hrl
Репозиторій вихідного кодуgithub.com/erlang/otp
Вебсайтwww.erlang.org

Erlang (Ерла́нґ /ˈɜːrlæŋ/ UR-lang) — мова функційного програмування з динамічною типізацією, призначена для розробки програм для різного роду розподілених і багатониткових систем. Розроблена і підтримується компанією Ericsson. Мова містить в собі засоби породження паралельних процесів та їхньої взаємодії за допомогою посилання асинхронних повідомлень, без використання блокувань.

Мова орієнтована на розробку розподілених відмовостійких застосунків, які забезпечують паралельну обробку запитів в режимі реального часу. Мова набула поширення в таких областях, як телекомунікації, банківські системи, електронна комерція, комп'ютерна телефонія і організація миттєвого обміну повідомленнями.

Програма транслюється в байт-код, який виконується віртуальною машиною, що забезпечує переносність. Одночасно розробниками випускається OTP (Open Telecom Platform) — супутній набір бібліотек і компонентів для розробки розподілених систем на мові Erlang. Код проєкту поширюється під модифікованою вільною ліцензією MPL.

Свій синтаксис і деякі концепції, мова Erlang успадкувала від мови логічного програмування Пролог. Мова підтримує зіставляння зі взірцем, обробку виключень, спискові та бінарні генератори, анонімні функції, функції вищого порядку, обмін повідомленнями між процесами. Препроцесор підтримує роботу з макросами і включення заголовкових файлів.

Популярність мови Erlang зросла у зв'язку з розширенням області використання (телекомунікаційні системи) на паралельні розподілені системи з високим навантаженням, які обслуговують мільйони користувачів WWW, такі як чати, системи керування веб-контентом, веб-сервери і розподілені бази даних, що вимагають масштабування. Erlang використовуються в деяких NoSQL-базах даних високої доступності[2].

Назва та історія

[ред. | ред. код]

Серед малообізнаних з історією мови Erlang людей поширена думка, що назва «Erlang» розшифровується як ERicsson LANGuage. Насправді мова отримала свою назву на честь Агнера Крарупа Ерланга[en], данського математика, який працював у галузі телекомунікацій. Існує одиниця вимірювання телекомунікаційного трафіку, яка також називається Erlang[en].[3]

На створення Erlang вплинули ML, Miranda[en], Ada, Модула-2[en], CHILL, Пролог. Окрім того, на спосіб оновлення програмного забезпечення вплинули Smalltalk і пропрієтарні мови EriPascal та PLEX, якими послуговувались в Ericson[4].

Мова Erlang була створена в 1986 році в лабораторії компанії Ericsson спільно Джо Армстронгом (Joe Armstrong), Робертом Вірдінгом (Robert Virding) і Майком Вільямсом (Mike Williams), і в 1998 році переведена в розряд відкритих проєктів. Завдяки початковій орієнтації на створення застосунків для паралельної обробки запитів в режимі реального часу мова набула поширення в таких областях як телекомунікації, банківські системи, електронна комерція, комп'ютерна телефонія і організація миттєвого обміну повідомленнями[5].

Роберт Вірдінг та Джо Армстронг, 2013 рік

У 1998 році топ-менеджмент Ericsson вирішив не брати на себе зобов'язань з розробки та підтримки власної мови програмування, натомість зосередившись на Java. Використання Erlang було заборонено у нових проєктах Ericsson Radio AB у зв'язку з реалізацією плану аутсорсингу програмної технології компанії Rational Inc.[6]

Це рішення дуже сильно вплинуло на майбутнє Erlang: воно призвело до відкриття коду Erlang під відкритою ліцензією EPL (аналог Mozilla Public License), а також послужило головною причиною початку поширення мови за межами компанії, що його створила. Основним запереченням проти відкриття вихідного коду було вирішення питань щодо патентів, але ці труднощі були подолані. Незабаром багато хто з основних розробників покинув Ericsson, щоб організувати власний бізнес — Bluetail AB.[4]

На початку 2000-х років наукові кола стали виявляти інтерес до Erlang. З 2002 року став проводитись щорічний Erlang Workshop. Ericsson продовжував спонсорувати проєкт HiPE (від англ. High-Performance Erlang — високопродуктивний Erlang). Проєкт HiPE займався ефективною реалізацією мови та інструментами для перевірки типів, і створений у групі проєкту компілятор в машинний код, який входить у постачання версії Erlang/OTP, вільно розповсюджується з 2001 року. Роботи, пов'язані з Erlang, ведуть інші заклади вищої освіти. Інструменти для рефакторингу створені в Кентському університеті у Великій Британії та університеті Лоранда Етвеша в Угорщині, інструменти для різних видів тестування — у Мадридському політехнічному університеті, технічному університеті Чалмерса та Гетеборгському університеті.

Коли системи з симетричною багатопроцесорністю тільки починали завойовувати ринок серверів і настільних комп'ютерів, кидаючи виклик розробникам програмного забезпечення, вже в 2006 перша версія Erlang з підтримкою SMP була випущена спільними зусиллями команди OTP з Ericsson і команди HiPE. Незабаром після цього вийшла перша майже за десятиліття велика монографія з Erlang: «Programming Erlang» Джо Армстронга, після чого багато розробників «відкрили» для себе Erlang/OTP, і мова стала набирати популярності.[7]

Процес розвитку мови включає в себе розгляд пропозицій з розвитку EEP (Erlang Enhancement Proposal). За допомогою цих пропозицій Erlang-спільнота вносить зміни у стандартну подачу Erlang. З внесеними пропозиціями можна ознайомитися на веб-сторінці erlang.org/eeps.

Філософія

[ред. | ред. код]

За свідченнями Майка Вільямса, Erlang створювався для вирішення трьох проблем розробки розподілених систем м'якого реального часу з високим ступенем паралелізму:

  • можливості швидкої та ефективної розробки ПЗ;
  • отримання системи, стійкої до програмних та апаратних збоїв;
  • можливості оновлення системи «на льоту», без простою обладнання.

За словами Вільямса, філософія, якої дотримувались розробники Erlang, пасує і для написання програмного забезпечення цією мовою:[8]

  • Знайдіть правильні методи. Проєктуйте з використанням прототипів.
  • Самих лише ідей недостатньо: треба вміти їх реалізовувати і знати, що вони працюють.
  • Робіть помилки в невеликому масштабі, а не у виробничому проєкті.

Оригінальний текст (англ.)

[

  • Find the right methods — Design by Prototyping.
  • It is not good enough to have ideas, you must also be able to implement them and know they work.
  • Make mistakes on a small scale, not in a production project.
] Помилка: {{Lang}}: текст вже має курсивний шрифт (допомога)

Більшість мов, створених раніше за Erlang, були розроблені без попереднього знаходження застосування, тоді як Erlang був розроблений спеціально на основі вимог до розподілених, стійких до відмов, паралельних систем реального часу. З розвитком Інтернету виявилося, що багато програм мають аналогічні вимоги, що пояснює зростання інтересу до мови.[9]

Секрет високої стійкості до відмов полягає у використанні ізольованих один від одного легких процесів, пов'язаних лише механізмом обміну повідомленнями та сигналами виходу. Принципи розробників на Erlang стосовно обробки помилкових ситуацій у процесах, можна виразити у вигляді висловлювання: «Let it crash»[10]. Пов'язано це з тим, що в Erlang-системі легко стежити за завершенням процесу, завершувати процеси, пов'язані зі збоями, і запускати нові процеси.[11]

Особливості

[ред. | ред. код]

Синтаксис успадкований від мови Prolog. Підтримує модулі, поліморфні функції, зіставлення за шаблоном, анонімні функції, умовні конструкції, структури, обробку винятків, оптимізацію хвостової рекурсії.

Головна риса Erlang — модель легких процесів. Процеси є дешевими, їхнє створення вимагає таку кількість ресурсів, що їх можна порівняти з викликом функції. Єдиним способом взаємодії процесів є асинхронний обмін повідомленнями. Процес може встановити зв'язок (link) з іншими процесами і за вибором або отримувати повідомлення про їхнє дострокове завершення з вказанням причини або розділити їхню долю. Процес має свою «поштову скриню», звідки може вибірково читати повідомлення. Мова програмування Erlang сприяє створенню великої кількості конкурентних процесів. Процеси ізольовані та не мають спільного стану.

Процес проєктування полягає в ітеративному розбитті системи на ієрархії підсистем, які конкурентно взаємодіють, доки складові не стануть достатньо простими для реалізації.

Сув'язь «Процеси + повідомлення» на відміну від сув'язі «Об'єкти + Інтерфейси + Успадкування» часто дає компактніші рішення. Відсутність потреби блокування доступу до стану процесу для синхронізації їхньої взаємодії суттєво спрощує програмування. Для конкурентного ресурсу зазвичай створюється процес-монітор, через який здійснюється взаємодія з ресурсом.

Також важливий момент полягає у формулі «let it crash» («нехай процес впаде»). Замість перехоплювати помилки і намагатися продовжувати роботу, частина програми, що містить ризикований код, відокремлюється у незалежний процес-камікадзе, який система вбиває у випадку виникнення помилки, при цьому батьківський процес отримує повідомлення про смерть нащадків і, за потреби, може перезапускати ці процеси. Такий підхід позбавляє код численних перевірок.

Високорівневі конструкції

[ред. | ред. код]

Erlang є декларативною мовою програмування, яка використовується більше для опису того, що має бути обчислено, ніж як. Наприклад, визначення функції, яке використовує зіставлення зі зразком для вибору одного з варіантів обчислення або вилучення елемента даних зі складової структури нагадує рівняння. Зіставлення зі взірцем поширене навіть на бітові рядки, що спрощує реалізацію телекомунікаційних протоколів.[8]

Функції є об'єктами першого класу Erlang. У мові також широко застосовуються характерні для функціональної парадигми програмування спискові включення (генератори списків).[8]

Паралельні обчислення

[ред. | ред. код]
Обмін повідомленнями в мові Erlang

Особливістю мови Erlang є застосування легковагих процесів відповідно до моделі акторів. Такий підхід дозволяє виконувати одночасно сотні тисяч і навіть мільйони таких процесів, кожен з яких може мати скромні вимоги до пам'яті. Процеси ізольовані один від одного і не мають загального стану, але між ними можна встановити зв'язок та отримувати повідомлення про їхній стан.[8]

Для взаємодії процесів використовують асинхронний обмін повідомленнями. Кожен процес має свою чергу повідомлень, обробка якої використовує зіставлення із взірцем. Процес, що надіслав повідомлення, не отримує повідомлення про доставку, навіть якщо ідентифікатор процесу одержувача недійсний або одержувач ігнорує повідомлення. Таким чином, відповідальність за правильно організовану взаємодію між процесами лежить на розробникові.[9]

Наприклад, при реалізації на Erlang мережевого чату, структура програми може безпосередньо відображати одночасність дій користувачів щодо обміну повідомленнями шляхом запуску нових процесів. Ефективність передачі повідомлень зберігається зі збільшенням кількості процесів, а вимоги до пам'яті мінімізуються за рахунок того, що легковагими процесами керує віртуальна машина, а не засоби операційної системи.[8]

Розподілені обчислення

[ред. | ред. код]

Erlang з самого початку проєктувався для розподілених обчислень та масштабованості. Підтримка паралелізму вбудована в синтаксис та семантику мови, тому побудову системи можна вести, абстрагуючись від конкретного місця обчислень. У стандартній поставці Erlang може налагодити зв'язок процесів протоколу TCP/IP незалежно від підтримуваних ним платформ (операційних систем).[8]

Запущений екземпляр емулятора Erlang називається вузлом (node). Вузол має ім'я і «знає» про існування інших вузлів на цій машині або у мережі. Створення та взаємодія процесів різних вузлів не відрізняється від взаємодії процесів всередині вузла. Програми, написані на Erlang, здатні працювати на кількох вузлах. Вузлами можуть бути процесори, багато ядер одного процесора, і навіть цілий кластер машин. Для створення процесу на іншому вузлі процесу достатньо знати його ім'я і, без особливих підстав, він може не цікавитися фізичним розташуванням процесу, що з ним взаємодіє. Синтаксис відправки повідомлення процесу на своєму вузлі та віддаленому однаковий.[8]

Завдяки вбудованим у мову можливостям розподілених обчислень об'єднання в кластер, балансування навантаження, додавання вузлів та серверів, підвищення надійності викликають лише невеликі витрати на додатковий код. За замовчуванням, вузли спроєктовані для роботи всередині відокремленого сегмента мережі (DMZ), але, якщо необхідно, комунікація між вузлами може відбуватися із застосуванням протоколу SSL, захищеного криптографічними методами.[8]

М'який реальний час

[ред. | ред. код]

Програми високорівневою мовою Erlang можуть бути використані в системах м'якого реального часу (який іноді перекладають як «псевдореальний» або «квазіреальний»). Автоматизоване керування пам'яттю та збирання сміття діють у рамках одного процесу, що дає можливість створювати системи з мілісекундним часом відгуку (навіть незважаючи на необхідність збирання сміття), що не відчувають погіршення пропускної здатності при високому навантаженні.[8]

Гаряча заміна коду

[ред. | ред. код]

Для систем, які не можуть бути зупинені для оновлення коду, Erlang пропонує гарячу заміну коду (англ. hot code upgrade). При цьому в додатку можуть одночасно працювати старі і нові версії коду. У такий спосіб програмне забезпечення на Erlang може бути модернізовано без простоїв, а виявлені помилки виправлені.[12]

Опис мови

[ред. | ред. код]

Типи даних

[ред. | ред. код]

Типізація в Erlang є сильною та динамічною. Динамічна типізація була обрана для мови Erlang через те, що перші розробники були більше з нею знайомі.[13] На думку Джо Армстронга, статична типізація вимагала б дуже великих трудовитрат, зокрема, реалізувати систему гарячого дозавантаження коду було б дуже важко. Така типізація, коли можливі помилки типів виявляються лише під час виконання, не завадила можливості створювати системи з дуже високим рівнем доступності. Дані Erlang є незмінними: операції не переписують старі значення, що знаходяться в пам'яті. Якщо необхідно, модулі на Erlang можна забезпечити описами та визначеннями нових типів (що не впливають на компіляцію програми) для автоматичної перевірки типів за допомогою утиліти Dialyzer.[13]

Числа

[ред. | ред. код]

У Erlang є два типи числових літералів: цілі і з рухомою комою, наприклад: 125,4.5e-20. Крім звичайної нотації, числа можна задавати через символ ASCII (наприклад, $B означає 66) або разом із зазначенням системи числення з основою від 2 до 36 (у старих версіях — до 16), наприклад: 16#3f, 2#1010. У Erlang застосовуються цілі числа довільної точності та дійсні числа подвійної точності (64 біти), у стандарті IEEE 754—1985. Для роботи з числами можна використовувати модуль math, який містить звичайний набір математичних функцій та функцію math:pi/0, що повертає число .[14] Приклад обчислень в інтерактивній оболонці:

Erlang/OTP 23 [erts-11.2.2.4] [source] [64-bit] [smp:20:20] [ds:20:20:10] [async-threads:1] [hipe]

Eshell V11.2.2.4  (abort with ^G)
1> 123 / 23 + 12 * (2 + 3).
65.34782608695652
2> math:cos(math:pi()).
-1.0
3> random:uniform(10).
5

Атоми

[ред. | ред. код]

Атом — константа з ім'ям, що має бути взята в одинарні лапки, якщо не починається з малої літери або містить знаки, окрім літер, цифр, підкреслення, крапки та символу@. Поняття атома запозичене з Prolog та його можна вважати аналогом перерахувань (enum) в інших мовах програмування (без необхідності попередньої декларації).[15] Атоми використовуються майже виключно в порівняннях (та в зіставленні зі взірцем (en: Pattern matching)), та мають Erlang дуже ефективну реалізацію. Крім того, деякі атоми мають певний сенс у значеннях, що повертаються, і описі винятків. До них відносяться, наприклад: error, ignore, noreply, ok, reply, stop, undefined.

Бітові рядки та бінарні дані

[ред. | ред. код]

Бітовий рядок використовується для зберігання пам'яті нетипізованих даних. Рядки, що складаються з цілої кількості октетів, називаються бінарними (або двійковими) даними (англ. binaries). Синтаксис опису бітового рядка досить гнучкий, оскільки визначає значення бітів окремих діапазонів і може бути забезпечений модифікатором типу.[8] Декілька прикладів в інтерактивній командній оболонці:

1> <<23,89,120>>.
<<23,89,120>>
2> <<"ABC">>.
<<65,66,67>>
3> <<10,17,42:16>>.
<<10,17,0,42>>
4> <<$a, $b, $c>>. 
<<"abc">> 
5> <<1024/utf8>>.
<<208,128>>

Конструктори бітових рядків (англ. bitstring comprehension) аналогічні списковим генераторам, але працюють над бітовими рядками:

1> << <<bnot(X):1>> || <<X:1>> <= <<2#111011:6>> >>.
<<4:6>>

У цьому прикладі змінна X послідовно отримує біти числа 2#111011, які потім інвертуються операцією бітового заперечення bnot (від англ. binary NOT), внаслідок чого виходить число 4.[8]

Кортеж

[ред. | ред. код]

Кортеж (англ. tuple) — складений тип даних з фіксованою кількістю елементів. При доступі до елементів кортежу за допомогою вбудованих функцій, нумерація елементів починається з одиниці, а не з нуля. Перший елемент кортежу (атом) можуть використовувати для опису ролі кортежу у програмі — його називають тегом (англ. tag — «мітка»). В Erlang прийнято будувати різні типи даних на основі кортежів з тегами, це полегшує зневадження програми та вважається гарним стилем програмування.[8]

Для роботи з кортежами є декілька вбудованих функцій, наприклад:[8]

1> tuple_size({a, 1, "777"}).
3
2> element(1, {b, 2, 3, 4}).
b
3> setelement(1, {c, 5}, d). 
{d,5}

Список

[ред. | ред. код]

Список (англ. list) — складений тип даних зі змінною кількістю елементів. Для маніпуляції зі списками можна використовувати функції модуля lists стандартної бібліотеки. Формально список визначається як структура, яка має голову (англ. head) та хвіст (англ. tail), що виражається синтаксично у вигляді [HEAD|TAIL], де хвіст зазвичай є списком (можливо порожнім). Порожній список позначається [][8]

Списки можна записувати і більш звичним способом. Наступні записи еквівалентні:

1> [a|[b|[c|[]]]].
[a,b,c]

Для роботи зі списками можна використовувати конструктори списків[13](генератори списків — en: List Comprehensions), наприклад:

1> [X / 2 || X <- [1,2,3,4]].
[0.5,1.0,1.5,2.0]

Модуль lists стандартної бібліотеки містить функції для роботи зі списками (та рядками, оскільки рядок виду «test» в Erlang є списком кодів символів)[8], такі як знаходження найбільшого значення, сортування, зміни порядку елементів на протилежний, отримання суми елементів тощо. У наступному прикладі «склеюємо» два списки в один за допомогою конкатенації, і далі розбиваємо на два списки функцією lists:split/2:

1> lists:split(2, [l,2,3] ++ [4,5,6]).
{[l,2],[3,4,5,6]}

А це приклад того, що рядок виду «test» в Erlang є списком кодів символів:

1> "test" =:= [$t, $e, $s, $t].
true
2> "test" =:= [116, 101, 115, 116].
true
3> $t.
116

У модулі lists є також набір функцій вищого порядку, таких як lists:all/2, lists:any/2, lists:dropwhile/2, lists:filter/2, lists:foldl/3, lists:foldr/3, lists:map/2, lists:foreach/2.

lists:map/2 — функція вищого порядку, яка дає можливість застосувати визначену нами функцію до кожного елемента списку, в результаті отримаємо новий список:

third_degree(R) ->
  lists:map(fun(X) -> math:pow(X, 3) end, R).

В даному прикладі функція math: pow(X, 3) підносить до степеня 3 кожен елемент списку R.

lists:filter/2 — дана функція вищого порядку приймає два параметри: функцію (предикат), за допомогою якої ми відфільтровуємо наш список — другий параметр. Повертає новий список — результат обробки фільтром вхідного списку.

multiple_three(List) ->
  lists:filter(fun(X) -> X rem 3 =:= 0 end, List).

В даному прикладі з вхідного списку List у новий — вихідний список попадуть лише елементи, кратні трьом.lists:foldl/3 — функція лівої згортки (англ. fold — згорнути, «l» від англ. left — лівий) приймає 3 аргументи:

  • функцію згортання
  • початкове значення акумулятора
  • перелік

Функція згортання приймає два аргументи: поточний елемент списку та поточне значення акумулятора. І повертає нове значення акумулятора.

Класичні приклади — обчислення суми та добутку елементів масиву:

1> Numberlist = [11, 12, 13, 14, 15].
[11, 12, 13, 14, 15]
2> lists:foldl(fun(Item, Acc) -> Acc + Item end, 0, Numberlist).
65
3> lists:foldl(fun(Item, Acc) -> Acc * Item end, 1, Numberlist).
360360

Наступний приклад ілюструє роботу функції правої згортки lists: foldr (англ. fold — згорнути, «r» від англ. right — правий), першим параметром якої має бути функція:

1> D = fun(V, A) -> V / A end. % функція D - ділення V на A
#Fun<erl_eval.12.82930912>
2> lists:foldr(D, 1, [1, 2, 4, 8]). 
0.25
3> 1/(2/(4/(8/1))). 
0.25

Результат виконання згортки справа наліво (у рядку 2) тотожний ланцюжковому поділу (рядок 3). Другий параметр foldr — початкове значення так званого акумулятора. Для кожного елемента списку (справа ліворуч) до елемента та акумулятора застосовується функція, задана першим аргументом foldr, а значення записується в акумулятор. За вичерпанням списку, функція повертає значення акумулятора. Функція є досить потужним засобом, якщо врахувати, що акумулятор може бути списком або кортежем.[9]

Зазвичай користуються foldl — тому, що операція отримання елементу-голови списку виконується за константний час, а для отримання елементу з кінця списку потрібно пройтись через весь список[16].

Рядок

[ред. | ред. код]

У Erlang немає самостійного типу для рядків — внутрішньо рядки є списками кодів символів. Рядок "Привіт!" рівносильний (у відповідному кодуванні) списку [1055,1088,1080,1074,1077,1090,33]. Для правильної інтерпретації списків кодів символів як рядків в інтерактивній консолі — запускайте з відповідним параметром: $ erl +pc unicode. Erlang підтримує Unicode як у рядку, так і запису окремого символу ($X, де X — деякий символ).[9]

Бінарні послідовності записуються та відображаються, як послідовності цілих чисел або рядків, поміщені між подвійними символами < та >. Для прикладу:

1> <<5,10,20>>.
<<5,10,20>>
2> <<"hello">>.
<<"hello">>

term_to_binary(Term) -> Bin — Конвертує будь-який терм Erlang у бінарну послідовність.[16]

Бінарна послідовність, вироблена за допомогою term_to_binary, зберігається в так званому зовнішньому форматі терму. Терми, які були конвертовані в бінарні послідовності з використанням term_to_binary, можуть бути збережені в файли, передані в повідомленнях по мережі тощо, а початковий терм, з якого вони були зроблені, може бути відновлений пізніше. Це надзвичайно корисно для збереження складних структур даних у файли або надсилання складних структур даних на віддалені машини.[16]

1> B = term_to_binary("useful").
<<131,107,0,6,117,115,101,102,117,108>>
2> binary_to_term(B).
"useful"

Атоми і рядки зовні досить схожі, але мають різні реалізації. Тоді як атоми можна лише порівнювати, рядки підтримують багато інших операцій, їм є безліч функцій в модулях lists і string. Рядок може виконувати функції атома, але пам'ять, яку займає рядок, пропорційна його довжині, тоді як атоми зберігаються в системній таблиці і на кожне зберігання/пересилання атома в програмі припадає лише декілька байтів, незалежно від довжини атома. Порівняння двох атомів — це порівняння двох внутрішніх ідентифікаторів, яке виконується за одну операцію, тоді як порівняння рядків передбачає поелементний прохід, тобто порівняння кодів символів — елементів списків.[8]

Логічні значення

[ред. | ред. код]

Для значень булевої логіки істинне та хибне в Erlang застосовуються атоми true (істинне) і false (хибне), які використовуються операціями порівняння, логічними операціями, вбудованими функціями.[8] Приклад:

1> 2 < 3.
true
2> is_boolean(125).
false

Функціональний об'єкт (Fun)

[ред. | ред. код]

Fun-вираз дозволяє створити анонімну функцію (лямбда-функцію), яка використовується, наприклад, для передачі аргументом в іншу функцію. За допомогою fun можна також отримати функціональний об'єкт для функції модуля.[9] Приклади:

1> lists:map(fun(X) -> X + 1 end, [1, 2, 3]).
[2,3,4]
2> Belongs = fun lists:member/2.
#Fun<lists.member.2>
3> Belongs(a, [a, b]).
true

Запис

[ред. | ред. код]

Щоб позначати окремі елементи кортежів (полегшити роботу з великими кортежами) і уникнути помилок при написанні програми, в Erlang було додано синтаксичний цукор — синтаксис записів (англ. record). Записів не існує в час виконання — на етапі компіляції записи перетворюються (транслюються) в кортежі — ключі губляться, відповідно, в час виконання існують лише значення записів. Для роботи з записами, необхідно спочатку дати опис запису директивою -record, наприклад, для запису user опис може бути наступним[17]

-record(user, {login = "anonymous", email, password}).

З цього опису компілятор дізнається, що маються на увазі кортежі з чотирьох елементів, в яких елементи з другого по четвертий відповідають полям login, nick, password запису з ім'ям user (визначається атомом в першому елементі кортежу). Значенням за замовчуванням для поля login є рядок "anon". Якщо значення за замовчуванням не вказано явно, таким є атом undefined.

Створення записів та вилучення елементів запису завжди вимагає явної вказівки імені запису:[17]

R0 = #user{}, % всі поля отримують значення за замовчуванням
R1 = #user{login = "user1", email = "user@mail.com", password = "secret"}, % Задаємо значення для всіх полів

Оновлення одного значення в наявному записі: R2 = R1#user{login = "user2"},

оновлення двох значень в наявному записі: R2 = R1#user{login = "user2", email = "neo@matrix.org"},

отримати значення по ключу з запису Nick2 = R2#user.nick..

Карта

[ред. | ред. код]

Асоціативний масив (словник) (en: map) зберігає пари виду «(ключ, значення)». І ключем, і значенням може бути будь-який терм Erlang.[18]

Map = #{a => 2, b => 3, c=> 4, "a" => 1, "b" => 2, "hi" => 42},
Key = "hi",
maps:find(Key,Map).
{ok,42}

Інші типи

[ред. | ред. код]

У мові Erlang є інші типи даних. Тип посилання (англ. reference) є унікальним у межах вузла/кластера з'єднаних вузлів в середовищі часу виконання Erlang. Посилання створюється викликом функції make_ref/0 і може повторитися через 282 викликів цієї функції. Посилання можна порівнювати на рівність, а застосовуються вони для одноразових позначок або «чарівного печива»[17]

Ідентифікатор порту (англ. port identifier) визначає порт для зв'язку із зовнішнім по відношенню до Erlang-системи світом. Порт дозволяє процесу, що його створив-власнику (так званому приєднаному процесу) обмінюватися бінарними повідомленнями зі сторонніми програмами і ОС способом, прийнятим в даній операційній системі.[17][19]

Ідентифікатор процесу (англ. Pid), як і випливає з його назви, ідентифікує процес, що породжується різними функціями spawn. Ідентифікатор можна вважати унікальним під час роботи Erlang-системи, але в системах, що довго працюють, можуть все-таки бути використані повторно, що зазвичай не є проблемою на практиці.[17]

Вбудовані функції для роботи з типами

[ред. | ред. код]

Для перетворення типів використовуються вбудовані функції (BIF,англ. builtin function) виду x_to_y («з x в y»), а для перевірки належності значення того чи іншого типу — функції виду is_x («є_x»):

1> atom_to_list(hello).
"hello"
2> list_to_binary("world").
<<"world">>
3> tuple_to_list({1,2,3,4}).
[1,2,3,4]
1> is_integer(3).
true
2> is_tuple("abc").
false
3> is_integer(3.0).
false

Операції

[ред. | ред. код]

Арифметичні операції

[ред. | ред. код]

Erlang надає найбільш поширені арифметичні операції для цілих чисел і чисел з плаваючою комою:

Позначення Операція, що виконується приклад Результат прикладу
+ Унарний плюс +3 3
- Унарний мінус -3 -3
+ Додавання 2+3 5
- Віднімання 7-3 4
* множення 1.2*0.4 0.48
/ Ділення 5 / 3 1.6666666666666667
div Цілочисельне ділення 5 div 3 1
rem Залишок від ділення 5 rem 3 2

Всі ці операції лівоасоціативні. Унарні операції мають найвищий пріоритет, потім слідує множення і ділення, найменший пріоритет у складання та віднімання. При необхідності ціле може приводитися до типу з рухомою комою.[8]

Бітові операції

[ред. | ред. код]

Бітові операції працюють над цілими числами і дають в результаті ціле число.[8]

Позначення Операція, що виконується приклад Результат прикладу
bnot Побітове заперечення bnot (2#1000) -9
band Побітове І 2 band 3 2
bor Побітове АБО 1 bor 2 3
bxor Побітове виключне АБО 5 bxor 3 6
bsr Побітовий зсув вправо 32 bsr 2 8
bsl Побітовий зсув вліво 1 bsl 5 32

Логічні операції

[ред. | ред. код]

Логічні операції працюють над логічними значеннями true (істинне) і false (хибне), одержуваними в результаті порівнянь та застосування функцій перевірки типу:[8]

Позначення Операція, що виконується приклад Результат прикладу
not Заперечення (НЕ) not true false
and Кон'юнкція (І) true and (1 < 5) true
andalso Аналогічно and, але не обчислює другий операнд, якщо перший false false andalso (1 < 5) false
or Диз'юнкція (АБО) is_atom("1") or is_atom(1) false
orelse Аналогічно or але не обчислює другий операнд, якщо перший true true orelse (1 < 5) true
xor Виключне АБО true xor true false

Операції порівняння

[ред. | ред. код]

Операції порівняння отримують два операнди, а результатом операції є логічне значення true або false. У Erlang є наступні операції: == (рівно), =:= (рівно, враховуючи тип — використовується для порівняння чисел), /= (не рівно), =/= (не рівно, враховуючи тип — використовується для порівняння чисел), =< (менше або дорівнює), < (менше), > (більше), >= (більше або дорівнює):

== перевірка на рівність (не враховуючи тип):

1> 5 == 5.
true
2> 5 == 5.0.
true

=:= перевірка на рівність (враховуючи тип):

4> 5 =:= 5.
true
5> 5 =:= 5.0.
false

Можна порівнювати значення різних типів, але вони вважаються в Erlang впорядкованими наступним чином:[8] число < атом < посилання < функція < порт < ідентифікатор процесу < кортеж < список < бінарні дані

Списки вважаються впорядкованими в лексикографічному порядку, а кортежі порівнюються за довжиною, і лише потім у лексикографічному порядку.[8]

Змінні

[ред. | ред. код]

Змінні служать для зберігання значень простих і складових типів. Ім'я змінної починається з великої літери (у спеціальних випадках — з підкреслення) і може містити букви, цифри, підкреслення. Змінній можна присвоїти значення лише один раз — ця властивість мови програмування називається одиничним присвоєнням (англ. single assignment). До переваг одиничного присвоєння можна віднести усунення необхідності в блокуваннях, а також спрощення налагодження програми[20]

Відбувається обчислення(редукція) аргументів функції перед застосуванням функції до цих аргументів (вхідних параметрів).

Область видимості змінної поширюється від її визначення (en: bind — зв'язування) до закінчення клози функції. Приклад:

binomial(X) ->
  Y = X * X,
  X + Y.

prod([1|T]) -> prod(T);
prod([Y|T]) -> Y * prod(T);
prod([])    -> 1.

У цьому прикладі область видимості X — весь опис функції binomial/1, а Y — від присвоєння до кінця опису. Змінна Y у другій клозі функції prod/1 не має відношення до змінної Y з binomial/1: її область видимості поширюється до кінця цієї клози.[8]

При виході обчислень за межі області видимості змінної, пам'ять, зайнята її вмістом, може бути звільнена у процесі збирання сміття, якщо значення змінної не використовується в іншій частині програми.[17]

Зіставлення зі зразком (en: Pattern Matching) використовується в Erlang для присвоєння (у тому числі, при роботі з параметрами функцій), управління потоком виконання програми, отримання значень складових типів, вибору повідомлення з черги. У лівій частині порівняння (або в заголовку функції) можуть бути пов'язані (вже мають значення) і незв'язані (отримуючі значення) змінні, а також літерали (атоми, числа, рядки). В результаті виконання порівняння може виявитися успішним (у цьому випадку змінні зв'язуються зі значеннями) та неуспішним — змінні залишаються непов'язаними. У зразку можуть бути змінні, значення яких для зразка байдуже: їх імена записуються з підкреслення. Змінні, ім'я яких починається з підкреслення, це звичайні змінні, крім того, що компілятор не буде скаржитися, якщо вони не використані. Не можна прив'язувати до них значення більше одного разу.[8]

Функції

[ред. | ред. код]

Програми Erlang складаються з функцій, які викликають одне одного. Кількість вхідних аргументів (параметрів) функції називається арністю. При виклику функції заголовки функції зіставляються зі зразком. У разі збігу параметрів виклику формальні параметри зв'язуються з фактичними і виконується відповідна частина тіла функції. Запис варіанту обчислення функції для деякого зразка може називатися клозом від англ. clause, а визначення функції — це набір з одного або більше клозів.

Для уточнення зіставлення зі зразком у функціях можна використовувати охоронні вирази, які випливають після ключового слова when[9]. У прикладі нижче визначено функцію обчислення знака числа, яка розраховується в залежності від порівняння параметра з нулем:

sign(X) when X > 0 -> 1;
sign(X) when X == 0 -> 0;
sign(X) when X < 0 -> -1.

Erlang перебирає (матчить) клози функції в тому порядку, в якому вони записані (зверху вниз, зліва направо), доки не буде знайдено відповідний вираз (заголовок клози), який зматчиться.[9] В охоронних виразах можна використовувати лише обмежений набір вбудованих функцій, оскільки ці функції не повинні мати побічних ефектів.

Функції Erlang підтримують рекурсивні виклики. У разі коли визначення функції закінчується рекурсивним викликом (хвостова рекурсія), Erlang використовує оптимізацію: стек викликів не застосовується.[9]

Як параметром, і результатом функції може бути інша функція. У наступному прикладі функція одного аргументу повертає функцію додавання аргументу[8]

1> Plus = fun(X) -> fun(Y) -> X + Y end end. % Визначення функції, яка повертає функцію
#Fun<erl_eval.6.82930912>
2> Plus(2). % Функція повертає Fun-об'єкт
#Fun<erl_eval.6.82930912>
3> Plus(2)(3). % Такий синтаксис не працює
* 1 : syntax error before : '('
4> ( Plus(2) )(3). % Додаткові дужки дозволяють досягти необхідного результату
5
5> Plus2 = Plus(2), Plus2(3). % Те саме з використанням додаткової змінної
5

Умовні вирази

[ред. | ред. код]

Окрім матчингу та охоронних виразів у клозах функції, в Erlang є інші умовні вирази вибору: if та case. Вираз вибору дозволяє організувати зіставлення зі зразком усередині функції і зазвичай має наступний синтаксис:

if
 охорона1 -> вираз11, вираз12, ... ;
 охорона2 -> вираз21, вираз22, ... ;
 ...
 охоронаN -> виразN1, виразN2, ...
end

Тут охорона1— охоронний вираз (en: guards).[21] Охоронні вирази — розширення зіставлення зі зразком, що додає виразності. Без охоронних виразів ми не можемо визначати такі речі, як діапазон значень чи певні типи даних. У Erlang if — це вираз, який може мати декілька гілок. Гілки скануються послідовно, зверху донизу, допоки захисна послідовність не буде оцінена як true. Якщо охорона1 (перший guard в даному прикладі) поверне true одразу почнеться виконання відповідних виразів: вираз11, вираз12,… Після виконання цих виразів відбудеться вихід з блоку і наступні умови охорона2, …, охоронаN перевірятися не будуть. Якщо ж охорона1 поверне false, відбувається перехід до перевірки наступного охоронного виразу, і так доти, поки guard не поверне значення true, після чого будуть виконані відповідні, розділені комами вирази і вихід з блоку. Слід зауважити, що і тут в охоронному виразі можна застосовувати лише обмежений набір операцій та вбудованих функцій. Коми в охоронному вираженні працюють як операція andalso, крапка з комою — як orelse, наприклад:[9]

if
  X =< 0 -> io:format("менше або дорівнює нулю ~n", []), <<"менше або дорівнює нулю"/utf8>>;
  X > 0, X < 10 -> io:format("більше нуля та менше десяти ~n", []), <<"більше нуля та менше десяти"/utf8>>;
  X == 10; X == 11 -> io:format("дорівнює десяти або одинадцяти~n", []), <<"дорівнює десяти або одинадцяти"/utf8>>;
  true -> io:format("більше або дорівнює дванадцяти~n", []), <<"більше або дорівнює дванадцяти"/utf8>>
end.

Якщо жодна захисна послідовність не є істинною, виникає помилка виконання if_clause. При необхідності можна використовувати захисник true. В останній гілці, оскільки ця захисна послідовність завжди вірна, і вважається аналогом «інше».[16] Компілятор Erlang стежить за безпекою зв'язування змінних усередині умовних виразів, це ми можемо побачити на наступному прикладі:

-module(badexample).
-export([broken_if/1]).

broken_if(X) ->
  if
    X < 0 -> Z = -1;
    X >= 0 -> Y = 1
  end,
  Y * Z.

При спробі скомпілювати модуль виникають повідомлення про помилки, тому що в такому коді змінні не пов'язуються зі значенням в іншій клозі відповідно:

1> c(badexample).
badexample.erl:8: variable 'Y' unsafe in 'if' (line 4)
badexample.erl:8: variable 'Z' unsafe in 'if' (line 4)
error

Правильним було б визначити всі використовувані далі за кодом змінні у всіх гілках if-вирази.[9]

-module(if_else).
-export([compare/2, ascii/1, run/3]).

	compare(X, Y) ->
		Result = if
			X > Y -> greater;
			X =:= Y -> equal;
			X < Y -> less		
		end,
	io:format("	~p is ~p than ~p ~n", [X, Result, Y]).
		
		ascii(Letter) ->
		Code = if 
			Letter =:= 'A' -> 101;
			Letter =:= 'B' -> 102;
			true -> unknown
		end,
	io:format("	~p = ~p ~n", [Letter, Code]).
		
		run(X, Y, Letter) ->
		compare(X, Y),
		ascii(Letter).

Можна використовувати вираз case ... of для порівняння послідовності патернів. На відміну від виразу if, case ... of дозволяє використовувати охоронні вирази в пунктах співставлення. Якщо жодний вираз case не відповідає зразку (не матчиться), система виконання викличе помилку про відсутність відповідності: «no case clause matching». Використовувати шаблон підкреслення _ можна, щоб захопити будь-яке інше значення, яким не відповідали попередні ключі.[5]

case expression of
       pattern1 [when guard1] -> expr_seq1;
       pattern2 [when guard2] -> expr_seq2;
    ...
end.

Спочатку обчислюється expression, припустимо, що при цьому воно набуває значення Value. Далі Value співставляється з pattern1 (разом з опціональним контролером guard1), pattern2 і так далі, до першого успішного співставлення. Як тільки це станеться, виконується послідовність виразів (expr_seqN) і результат цього обчислення стає результатом всього даного виразу. Якщо жоден із патернів не підійде, то відбувається виняткова ситуація.[16]

-module(case_of).
-compile([export_all, nowarn_export_all]).
		
	admit(Person) ->
		case Person of
			{male, Age} when Age > 21 -> yes_with_cover;
			{female, Age} when Age > 21 -> yes_no_cover;
			{male, _} -> no_boy_admission;
			{female, _} -> no_girl_admission;
			_ -> unknown
		end.
		
	run(Person) ->
		Record = admit(Person),
		io:format("  ~p~n", [Record]).

Приклади

[ред. | ред. код]

В функціональних мовах програмування відсутні цикли, натомість використовується рекурсія.

Розглянемо функції обчислення факторіалу:

-module(fact).
-export([fac/1, func/1]).

fac(0) -> 1;
fac(N) when N > 0 ->
  N * fac(N-1).

Це рекурсія без хвостової оптимізації, вона добре працює для невеликої глибини рекурсії. Проте, стає проблематичною у випадку великої глибини рекурсії.

Чим більше операцій рекурсії — тим більше займе пам'яті дана програма у стеку при виконанні, дана рекурсія розгортається у стек — відповідно, чим більше рекурсивних викликів — тим більше пам'яті займе дана програма при виконанні.[16]

Хвостова рекурсія — це рекурсія, яка викликається останньою інструкцією, таким чином стек залишається незмінним (або ж практично незмінним) і функція може працювати перманентно без зупинки.[16]

func(N) -> func(N,1).
func(0,A) -> A;
func(N,A) when N > 0 -> func(N-1, N*A).

Хвостова рекурсія незалежна від кількості рекурсивних викликів — в пам'яті залишаються лише два числа. Спробуємо написати функцію обчислення N-ного числа Фібоначчі на Erlang двома шляхами-алгоритмами. Спочатку звичайна рекурсія.

-module(fibonacci).
-export([fib_usual/1, fib_tail/1]).

fib_usual(1) -> 0;
fib_usual(2) -> 1;
fib_usual(N) when N > 0 ->
  fib_usual(N - 1) + fib_usual(N - 2).

Дана функція тримає всі числа-проміжні результати одночасно у памяті, і кількість рекурсивних викликів функції — активних процесів — зростає надто стрімко. Хвостова рекурсія в даному випадку пишеться з використанням двох тимчасових змінних в якості параметрів нашої функції. Такі змінні називаються акумуляторами та використовуються для зберігання проміжних результатів наших обчислень, щоб не зберігати багато незакінчених обчислень (процесів, які очікують результат розрахунків від інших процесів) у пам'яті.

fib_tail(N) when is_integer(N), N > 0 ->
  fib_tail(N, 1, 0).
fib_tail(0, A1, _) -> A1;
fib_tail(N, A1, A2) ->
  fib_tail(N - 1, A2, A1 + A2).

У памяті тримаються лише три числа та займається обчисленнями лише один процес.

Додамо декілька функцій, які працюють рекурсивно зі списком і виконують такі звичні для всіх нас завдання:

- визначення довжини масиву;

- знаходження максимального (мінімального) елемента масиву;

- обчислення суми всіх елементів масиву.

1. Визначення довжини масиву:

- спочатку звичайна рекурсія

len_list([]) -> 0;
len_list([_]) -> 1;
len_list([_H|T]) when is_list([_H|T]) ->
  1 + len_list(T).

- хвостова рекурсія з акумулятором

length_tail(List) when is_list(List)->
  length_tail(List, 0).
length_tail([], Acc) -> Acc;
length_tail([_|Tail], Acc) ->
  length_tail(Tail, 1 + Acc).

2. Знаходимо максимальний елемент масиву:

- спочатку звичайна рекурсія

max_el([Last]) when is_number(Last) -> Last;

max_el([H | T]) when hd(T) >= H ->
  max_el(T);
max_el([H | T]) ->
  max_el([H | tl(T)]).

- та сама функція, з допомогою хвостової рекурсії і акумулятора

tail_maxx([H | T]) when is_number(H)->
  tail_maxx(T, H).
tail_maxx([], Acc) when is_number(Acc) ->
  Acc;
tail_maxx([H | T], Acc) when H >= Acc ->
  tail_maxx(T, H);
tail_maxx([_ | T], Acc) ->
  tail_maxx(T, Acc).

3. Підрахуємо суму елементів масиву:

- звичайна рекурсія

sumx([]) -> 0;
sumx([H | T]) -> H + sumx(T).

- підрахунок суми елементів масиву з допомогою хвостової рекурсії

tail_sum(List) -> tail_sum(List, 0).

tail_sum([], Acc) -> Acc;
tail_sum([H | T], Acc) -> tail_sum(T, Acc + H).

Один з алгоритмів сортування — швидке сортування[12]

-module(qsort).
-export([qsort/1]).

 qsort([]) -> [];
 qsort([Pivot|Rest]) ->
   qsort([Front || Front <- Rest, Front < Pivot]) ++ [Pivot] ++
     qsort([Back || Back <- Rest, Back >= Pivot]).

У цьому прикладі функція qsort викликається рекурсивно до вичерпання всіх елементів. Вираз [Front || Front <- Rest, Front < Pivot] збирає список Front з елементів Rest таких, що елемент Front менший за Pivot. Оператор ++ склеює списки.

Препроцесор та макроси

[ред. | ред. код]

Препроцесор Erlang (EPP) дозволяє вкладати файли з вихідним кодом один в інший, визначати макроси та здійснювати прості та параметризовані макропідстановки.[8] Макрос визначається за допомогою директиви -define, а макропідстановка здійснюється вказівкою імені макроса та можливих параметрів після знака питання (?). Наступний приклад показує визначення та застосування параметризованого макроса:

-define(ZERO(X), X == 0).

is_zero(T) when ?ZERO(X) -> true;
is_zero(T) -> false.

Ім'я макроса зазвичай пишеться великими літерами. Визначення макроса має містити лексеми Erlang цілком (наприклад, спроба задати частину імені змінної за допомогою макроса викликає синтаксичну помилку). Макроси можуть використовуватися для підвищення зручності читання коду в охоронних виразах, для операторів налагодження і т. ін. Препроцесор має декілька визначених макросів, які не можна перевизначити: ?MODULE, ?MODULE_STRING, ?FILE, ?LINE, ?MACHINE.[22]

Заголовний файл (розширення .hrl) з визначеннями макросів та записів можна включити за допомогою директиви -include.[8]

Обробка помилок

[ред. | ред. код]

Для обробки виняткових ситуацій Erlang можна застосовувати конструкцію try ... catch, в загальному випадку записується в наступному вигляді[8]

try вираз для обчислення of
 зразок1 when охорона1 -> вираз1;
 зразок2 when охорона2 -> вираз2;
 ...
 зразокN when охоронаN -> виразN
catch
 класс1:зразокВикл1 when охоронаВикл1 -> виразВикл1;
 ...
 классМ:зразокВиклМ:Stacktrace when охоронаВиклМ -> виразВиклМ;
end

Як і у випадку case-виразу, вираз, що обчислюється, зіставляється зі зразком (частини між of і catch) для отримання результату.[9] Після ключового слова catch слідують частини обробки винятків, у яких на додаток до зразків винятків можуть бути вказані класи винятків (перед двокрапкою): error, throw та exit. Stacktrace — це список викликів функцій, обчислення яких виконувала програма, коли було створено виняток. Підкреслення може використовуватись як у зразку, так і в класі виключення. Наступний простий приклад ілюструє перехоплення помилки класу error при обчисленні квадратного кореня.

1> try math:sqrt(-1) catch error:Error -> {error, Error} end.
{error, badarith}
2> try math:sqrt(4) catch error:Error -> {error, Error} end.
2.0

Для створення винятків, визначених користувачем, використовується функція throw/1, яка приймає кортеж з більш детальним описом помилки,[9] що виникла і генерує виняток класу throw. Використання цієї функції небажане через погіршення зручності читання коду програми, але може знадобитися в деяких випадках при роботі з вкладеними структурами даних, наприклад, при розборі XML[8]. Винятки класу exit виникають у результаті виклику вбудованої функції exit/1 або сигналу виходу.

До розробки Річардом Карлссоном (Richard Carlsson) з команди проєкту HiPE описаного вище нового механізму обробки винятків (з'явився у версії R10B) в Erlang використовувалися catch-вирази.[8]

Модулі

[ред. | ред. код]

Код програми на Erlang можна розбити на окремі модулі. Модуль — це ім'я для набору функцій, організованих в одному файлі. Ім'я модуля має збігатися з ім'ям файлу (якщо відкинути розширення)[8]. Модуль можна відкомпілювати в байт-код як із командного рядка операційної системи, так і з командної оболонки Erlang[13]. У файлі модуля можна записати оголошення функцій та директиви (іноді називаються атрибутами). Обов'язковим атрибутом є лише -module(атом_імені_модуля). Інший атрибут, що часто використовується — -export — застосовується для опису списку експортованих функцій, тобто функцій, які можна використовувати за межами модуля.

Функції в Erlang однозначно визначаються модулем, ім'ям та арністю. Наприклад, math:cos/1 відповідає функції cos з модуля math приймає один аргумент. Викликати функцію можна так: math:cos(1.2)[8]

Вихідний текст модуля компілюється в BEAM-файл — файл, що містить байт-код віртуальної машини BEAM (англ. Bogdan’s/Björn's Erlang Abstract MachineBogdan's/Björn's Erlang Abstract Machine[23]). У свою чергу, ERTS (англ. Erlang Runtime SystemErlang Runtime System — система часу виконання Erlang) виконує цей код.[9]

Процеси

[ред. | ред. код]

Основною абстракцією паралельного програмування в Erlang є процес. Процеси можуть породжувати інші процеси, виконуватися одночасно, обмінюватися повідомленнями, реагувати на завершення один одного.

Створення процесів

[ред. | ред. код]

Erlang має невеликий, але потужний набір примітивів для створення процесів і спілкування між ними.

Міжпроцесовий зв'язок працює за допомогою асинхронної системи передачі повідомлень, яка не має спільного доступу: кожен процес має «поштову скриньку» — чергу повідомлень, які були надіслані іншими процесами та ще не використані. Для отримання повідомлень, які відповідають бажаним паттернам, процес використовує примітив receive. Процедура обробки повідомлень перевіряє повідомлення по черзі на відповідність кожному паттерну, поки один з них не збігається. Коли повідомлення використано та видалено з поштової скриньки, процес відновлює виконання. Повідомлення може містити будь-яку структуру Erlang, включаючи примітиви (цілі числа, числа з плаваючою точкою, символи, атоми), кортежі, списки та функції.

Для створення нового процесу служить декілька вбудованих функцій (spawn та її аналоги)[24]. Функції повертають ідентифікатор процесу, який може використовуватися, наприклад, для надсилання повідомлень новоствореному процесу. В інтерактивній консолі erl можна отримати список процесів та іншу інформацію за допомогою функцій processes(). та i(). відповідно.[8]

Наведений нижче приклад коду показує вбудовану підтримку розподілених процесів:

%% Створимо процес та викличемо функцію web:start_server(Port, MaxConnections)
ServerProcess = spawn(web, start_server, [Port, MaxConnections]),

%% Створимо віддалений процес та викличемо функцію
%% web:start_server(Port, MaxConnections) на віддаленій ноді RemoteNode
RemoteProcess = spawn(RemoteNode, web, start_server, [Port, MaxConnections]),

%% Відправимо повідомлення процесу ServerProcess (асинхронно).
%% Повідомлення складається з кортежа з атомом "pause" та числом "10".
ServerProcess ! {pause, 10},

%% Отримаємо повідомлення, надіслані цьому процесу
receive
  a_message -> do_something;
  {data, DataContent} -> handle(DataContent);
  {hello, Text} -> io:format("Got hello message: ~s", [Text]);
  {goodbye, Text} -> io:format("Got goodbye message: ~s", [Text])
end.

Як показує приклад, процеси можуть бути створені у віддалених вузлах, і взаємодія з ними є прозорою у тому сенсі, що взаємодія з віддаленими процесами працює точно так само, як взаємодія з локальними процесами.

Розподіленість (en: Concurrency) підтримує основний метод обробки помилок в Erlang. Коли процес аварійно завершується, він акуратно завершує роботу та надсилає повідомлення керуючому процесу, який потім може вжити заходів, наприклад запустити новий процес, який бере на себе завдання старого процесу.

Передача та прийом повідомлень

[ред. | ред. код]

Як і мова Occam, Erlang використовує для відправки повідомлення синтаксис зі знаком оклику: Process_ID ! Message. Прийом повідомлення — тобто вилучення його з черги («поштової скриньки») процесу — виконується за допомогою receive-виразів, які зазвичай записуються таким чином[8]:

receive
  зразок1 when охорона1 -> вираз11, вираз12, ...;
  зразок2 when охорона2 -> вираз21, вираз22, ...;
  ...
  зразокN when охоронаN -> виразN1, виразN2, ...;
  Непов'язанаЗміннаДляІншихПовідомлень -> вираз1, вираз2, ...
end

Зустрівши такий вираз, інтерпретатор послідовно переглядає повідомлення з черги. Кожне повідомлення інтерпретатор зіставляє зі зразком і, якщо воно відповідає зразку, то обчислюються відповідні вирази. Коли всі повідомлення перебрані, і потрібного не виявилося, процес очікує нових повідомлень, після чого перебір черги повторюється. Якщо в receive-виразі відсутній зразок, якому задовольняє будь-яке повідомлення, такий вираз називається вибірковим receive-виразом.[8]

Обробка помилок та завершення процесів

[ред. | ред. код]

Процес можна зв'язати з іншим, у результаті між процесами встановлюється двонаправлене з'єднання (англ. link). Якщо один із процесів завершується ненормально, всім пов'язаним з ним процесам передається сигнал виходу (англ. exit signal). Процеси, що отримали сигнал, завершуються, поширюючи сигнал далі. Сигнал виходу є кортежем, елементами якого є атом 'EXIT' (вихід), ідентифікатор процесу, що завершився, і причину завершення процесу. Причина завершення передається ланцюжком процесів, що завершуються.[8]

Процес може здійснити перехоплення помилки (англ. trapping errors), якщо він має прапорець перехоплення виходу. Такий процес отримує сигнали виходу пов'язаних з ним процесів у вигляді звичайних повідомлень з аналогічною структурою кортежу. Перехоплений сигнал виходу більше не передається пов'язаним з процесом-перехоплювачем процесам. Сигнал виходу з причиною — атомом normal (нормальне завершення процесу) не викликає завершення пов'язаного процесу. Якщо ж причина — атом kill, процес завершується беззастережно (незалежно від прапорець перехоплення виходу), а пов'язаним з ним процесам як причина відправляється атом killed, що дає їм можливість зреагувати.[8]

Erlang має можливість встановити і однонаправлене з'єднання. При завершенні процесу, що спостерігається, процес-спостерігач отримує повідомлення із зазначенням причини завершення. Процес може зупинити себе сам чи зупинити інший процес, викликавши функцію exit.[8]

Ввід-вивід

[ред. | ред. код]

У планувальнику процесів Erlang-системи проблема вводу-виводу, властива багатьом іншим мовам паралельного програмування, вирішена достатньо елегантно. Управління вводом-виводом, інтегроване з планувальником, вже на нижньому рівні здійснюється на основі подій, що дозволяє програмі обробляти вхідні та вихідні дані без зайвих блокувань. Такий підхід вимагає меншого числа випадків установлення та розриву з'єднань, а також прибирає необхідність блокування та перемикання контексту. Спосіб частіше застосовується в системах з явними вимогами високої доступності та низького часу відгуку. Реалізація подієво-орієнтованого вводу-виводу вбудована в Erlang-систему. Це є перевагою при проєктуванні паралельних додатків.[17]

Стандартна бібліотека містить модуль io з функціями вводу-виводу. Такі функції містять в собі побічні ефекти. Вони полягають, наприклад, у появі виведеної інформації в консолі або у записуванні даних у файл на диску. Наприклад, функція io:format для форматованого виводу виводить рядок із підстановкою параметрів, повертаючи у разі успіху атом ok.[8]

1> io:format("приклад виводу : ~p~n", [1]). 
приклад виводу : 1
ok

Функції модуля io передбачають стандартний серверний інтерфейс вводу-виводу. Протокол вводу-виводу Erlang (англ. The Erlang I/O-protocol) детально визначає зв'язок клієнта і сервера, забезпечує двонаправлений зв'язок між клієнтами та серверами. Сервер вводу/виводу — це процес, який обробляє запити та виконує потрібне завдання, наприклад, на пристрої введення/виведення. Клієнт — це будь-який процес Erlang, який бажає читати або писати дані з/на пристрій введення/виведення.[25]

Бібліотеки

[ред. | ред. код]

Стандартна бібліотека модулів

[ред. | ред. код]

Згідно з офіційною документацією, стандартна бібліотека модулів STDLIB[14] є обов'язковою для включення до мінімальної системи Erlang/OTP[26] разом з ядром Erlang. Бібліотека містить модулі, що надають різноманітні функції для роботи з вбудованими типами та іншими структурами даних, вводу-виводу, звернення до середовища, для роботи з файловою системою, процесами тощо.

Згідно з офіційною документацією, стандартна бібліотека модулів STDLIB є обов'язковою для включення до мінімальної системи Erlang/OTP[26] разом з ядром Erlang. Бібліотека містить модулі, що надають різноманітні функції для роботи з вбудованими типами та іншими структурами даних, вводу-виводу, звернення до середовища, для роботи з файловою системою, процесами тощо.

Модуль array :

Модуль string розширює можливості модуля lists функціями для роботи безпосередньо зі списками символів, якими є рядки в мові Erlang.

Модуль dict (від англ. dictionary — словник) містить функції для асоціативного масиву. Наприклад, збереження, вилучення та видалення значення за ключем, з'єднання масивів тощо.

Модуль math містить математичні функції.

Модуль random містить функції для генерації псевдовипадкових чисел.

Модуль calendar забезпечує обчислення місцевого та універсального часу, дня тижня та багатьох функцій перетворення часу. Час є місцевим, коли він налаштований відповідно до поточного часового поясу та літнього часу. Час є універсальним, коли він відображає час на нульовій довготі без будь-яких поправок на літній час.

Модуль timer містить функції переведення інтервалів часу до мілісекунд, запуску подій за таймером та інші, пов'язані з часом, функції.

Модуль erlang містить вбудовані функції Erlang; по замовчуванню, функції з цього модуля є імпортованими в кожен модуль.

Модуль file забезпечує інтерфейс до файлової системи. Функції для відкриття, закриття, читання та запису файлів; перегляду директорій і т. д.

Модуль filelib містить утиліти вищого рівня, ніж модуль file. Цей модуль не підтримує «необроблені» імена файлів (тобто файли, імена яких не відповідають очікуваному кодуванню). Такі файли ігноруються функціями цього модуля. Допоміжні функції для маніпуляцій з файлами:

  • ensure_dir(Name) -> ok | {error, Reason} — Переконується, що всі батьківські каталоги для вказаного файлу або імені каталогу існують, намагаючись створити їх, якщо необхідно. Повертає OK, якщо всі батьківські каталоги вже існують або можуть бути створені. Повертає {error, Reason}, якщо якийсь батьківський каталог не існує та не може бути створений.
  • ensure_path(Path) -> ok | {error, Reason} — Переконується, що всі батьківські каталоги для вказаного шляху Path існують, намагаючись створити їх, якщо необхідно. На відміну від secure_dir/1, ця функція намагатиметься створити всі сегменти шляху як каталог, включаючи останній сегмент. Повертає OK, якщо всі батьківські каталоги вже існують або можуть бути створені. Повертає {error, Reason}, якщо якийсь батьківський каталог не існує та не може бути створений.

Модуль filename містить функції для аналізу та обробки імен файлів. Ці функції створені таким чином, щоб код Erlang міг працювати на багатьох різних платформах із різними форматами імен файлів. Назва файлу може бути короткою відносною назвою, наприклад foo.erl, довгою абсолютною назвою, включаючи позначення диска, назвою каталогу, як-от D:\usr/local\bin\erl/lib\tools\foo.erl, або будь-якими варіаціями між ними. Допоміжні функції для маніпуляцій з файлами:

  • absname(Filename) -> file:filename_all() — Перетворює відносне ім'я файлу та повертає абсолютне ім'я. Не робить спроб створити найкоротшу абсолютну назву, оскільки це може дати неправильні результати у файлових системах, які дозволяють посилання.
  • absname(Filename, Dir) -> file:filename_all() — Те саме, що absname/1, за винятком того, що каталог, до якого має бути віднесене ім'я файлу, вказано в аргументі Dir.

Модуль io забезпечує інтерфейс до стандартних серверів введення/виведення Erlang. Усі функції виведення повертають ОК, якщо вони успішні, або завершують роботу, якщо це не так. Функції в цьому модулі мають додатковий параметр IoDevice. Якщо він включений, це буде pid процесу, який обробляє протоколи введення/виведення. Зазвичай — IoDevice, який повертає file: open/2. Якщо IoDevice не вказано, використовується standard_io. Опис протоколів вводу-виводу є у розділі «Протокол вводу-виводу Erlang» у посібнику користувача.

Модуль io_lib — функції бібліотеки введення/виведення. Містить функції для перетворення в / і з рядків (списків символів). Вони використовуються для реалізації функцій в модулі io.

Модуль crypto забезпечує набір криптографічних функцій. Наявні хеш-функції:

  • SHA1, SHA2 — Secure Hash Standard [FIPS PUB 180-4].
  • SHA3 — Standard: Permutation-Based Hash and Extendable-Output Functions [FIPS PUB 202].
  • BLAKE2 — fast secure hashing.
  • MD5 — Message Digest Algorithm [RFC 1321].
  • MD4 — Message Digest Algorithm [RFC 1320].

Модуль crypto_app — додаток crypto. Метою програми crypto є надання Erlang API для криптографічних функцій. API знаходиться на досить низькому рівні, і є деякі відповідні функції API, доступні в public_key(3), на вищому рівні абстракції, який використовує криптододаток у своїй реалізації. Поточна реалізація крипто використовує nifs для інтерфейсу криптобібліотеки OpenSSL і може працювати з обмеженою функціональністю зі старими версіями, як OpenSSL 0.9.8c. Підтримка режиму FIPS вимагає принаймні версії 1.0.1 і встановлення OpenSSL із підтримкою FIPS. Рекомендується використовувати версію, яка офіційно підтримується проєктом OpenSSL. API-сумісні серверні модулі, такі як LibreSSL, також повинні працювати. Криптододаток щодня тестується принаймні з однією версією OpenSSL 1.0.1, 1.0.2, 1.1.0, 1.1.1 і 3.0. Режим FIPS також перевірено для 1.0.1 і 1.0.2. Використання OpenSSL 3.0 із механізмами або в режимі FIPS ще не підтримується програмою OTP/crypto. Вихідні версії OpenSSL можна завантажити з домашньої сторінки проєкту OpenSSL або з перелічених там сайтів-дзеркал.

Окрім цих найбільш важливих модулів, стандартна бібліотека містить багато інших, з якими можна познайомитися за документацією.[8]

Таблиці ETS та DETS

[ред. | ред. код]

Для організації колекцій в оперативній пам'яті в Erlang є модуль ETS (англ. Erlang Term Storage — «сховище термів Erlang»). ETS може зберігати чотири види колекцій: множина (англ. set), впорядкована множина (англ. ordered set), мультимножина (англ. bag), мультимножина з повтореннями (англ. duplicate bag).[9] Доступ до елементів колекцій відбувається ключовим полем кортежу(ключі, як і значення, можуть бути будь-яких типів). Упорядковані множини зреалізовані у вигляді бінарних збалансованих АВЛ-дерев, а інші колекції — з використанням хеш-таблиць.[8]

DETS-таблиці доповнюють функціональність ETS-таблиць (за винятком впорядкованих множин), дозволяючи зберігати дані у файлах.

Фреймворк OTP

[ред. | ред. код]
Дерево процесів Erlang

OTP (англ. Open Telecom Platform) є налагодженим набором корисних поведінок (англ. behaviours) процесів. Він використовується для створення серверних програм. OTP формалізує дії процесів та дозволяє будувати на їх основі OTP-додатки. У модулях ОТР визначено загальні, стандартизовані шаблони для конструювання паралельних додатків. Gen_server — інтерфейсний модуль для реалізації клієнт-серверної архітектури.[27] Загальний серверний процес (gen_server), реалізований за допомогою цього модуля, має стандартний набір інтерфейсних функцій і включає функції відстеження та звітування про помилки. Він вписується в дерево нагляду OTP. Найпопулярнішими поведінками є узагальнений сервер та спостерігач (англ. supervisor)[28]. Є й інші поведінки: кінцевий автомат, обробник подій[29].[9] OTP містить і інше сполучне програмне забезпечення (англ. middleware), наприклад, СУБД Mnesia.

OTP-поведінки поділяються на робочі процеси (англ. worker processes):

  • Що виконують власне обробку запитів,
  • Процеси-спостерігачі (англ. supervisors).

У завдання процесів спостерігачів входить стеження за робочими процесами та іншими процесами спостерігачами — нащадками. Дерева спостерігачів складають OTP-додаток (англ. application). Документація Erlang визначає OTP-додаток компонентом, який реалізує деяку функціональність, яка може бути незалежно запущена на виконання і зупинена як ціле, а також повторно використана в інших системах.[30] Розробник програми пише код модулів функцій зворотного виклику (англ. call-back module), в яких і знаходиться специфічна для цієї програми частина функціональності.[8]

Строго кажучи, OTP не є частиною мови Erlang. Однак, він настільки увійшов у культуру та практику розробників на Erlang, що часом між ними складно провести межу.[9]

Розробка графічного інтерфейсу користувача

[ред. | ред. код]

Розробка додатків з графічним інтерфейсом користувача (крім веб-інтерфейсів) може вестись за допомогою бібліотеки wxErlang — бібліотеки wxWidgets, портованої для Erlang. В стандартну поставку Erlang/OTP входить WxErlang. WxWidgets написаний на C++, тому перед розробниками wxErlang стояло завдання виразити засобами Erlang ієрархію об'єктів. Спрощуючи, у wxErlang класам відповідають модулі, а об'єктам — посилання, макросам на C ++ відповідають макроси Erlang. Деякі типи даних, для яких у C++ були використані класи, подаються в Erlang за допомогою інших типів даних, наприклад wxPoint задається у вигляді кортежу з двох елементів. Події wxErlang можна оброблити в Erlang або через функцію зворотнього виклику (англ. call-back functions), або в більш природному середовищі Erlang передачею повідомлень.[8]

Програмування на Erlang

[ред. | ред. код]

Інтерактивна оболонка

[ред. | ред. код]
Інтерактивна оболонка erl в окремому вікні

Інтерактивна оболонка (англ. shell) для Erlang може бути викликана в Unix-подібних системах командою erl, у Windows — werl.

Оболонка дозволяє:

  • вводити вирази та отримувати результат їх виконання, випробувати новий код,
  • займатися інтерактивним налагодженням,
  • керувати системою, що знаходиться в промисловій експлуатації, тощо.[17]

Також, оболонка дозволяє використовувати додаткові функції (команди), доступні тільки в ній. До прикладу, команда q(). здійснює вихід із оболонки із завершенням всього, що робить Erlang-система.[17]

В оболонці можна викликати BREAK-меню за допомогою Ctrl+C (у Unix-подібних ОС) або Ctrl+Break (у Windows). Це меню має різні команди, у тому числі a — негайна зупинка, c — продовження роботи в оболонці та інші інформаційні та допоміжні команди для роботи з Erlang-системою. Комбінація клавіш Ctrl+G викликає ще командне меню, яким можна, за необхідності, зупинити процес, що «завісив» оболонку, і повернутися в оболонку (i і потім c)[13]

Документування та оформлення коду

[ред. | ред. код]

Система EDoc може генерувати документацію з вихідного коду. Для документування коду модуля достатньо додати певним чином розмічений текст, а також файл overview.edoc для документації рівня проєкту (в останньому необов'язково використовувати знаки коментаря)[9]. Інструменти для роботи з кодом на Erlang, наприклад, erlang-режим в Emacs, мають на увазі деякі особливості щодо вживання символів коментаря. Коментарем вважається текст від знака відсотка (%) до кінця рядка в Erlang. Так, потрійний знак відсотка викликає вирівнювання лівим краєм, подвоєний — вирівнювання на рівні навколишнього коду, а одиночний знак відсотка використовується для позначення коментаря після коду, в кінці рядка[9]. Розробники Erlang виробили певні стильові угоди щодо організації та оформлення вихідного коду. Наприклад, хорошим стилем вважається зниження вкладеності синтаксичних структур, написання коротких модулів (менше 400 рядків коду) та функцій (не довше 15-20 рядків коду), використання осмислених імен для змінних та функцій тощо.[31]

Типи та аналіз коду

[ред. | ред. код]

Програма Dialyzer, розроблена в рамках проєкту HiPE. Вона входить до стандартної поставки і дозволяє виявити помилки (у тому числі помилки типізації) шляхом статичного аналізу коду. Програма TypEr, написана Тобіасом Ліндалом (Tobias Lindahl) та Костісом Сагонасом (Kostis Sagonas), є частиною Dialyzer. TypEr дозволяє перевіряти визначення типів функцій, звіряти вказаний у директиві -spec тип функції з її визначенням, виконати виведення типів. Програма виводить всі типи, що відповідають успішному застосуванню функції, в загальному випадку лише приблизно, в більш грубий бік. Використання функції будь-яким іншим чином обов'язково призведе до помилки часу виконання. У наступному прикладі показаний синтаксис визначення типу (директива -type), оголошення типу полів запису та директива -spec разом з визначенням функції[8]:

-type(user_status() :: disabled | enabled). % статус - один з двох атомів

-record(user, {login="anon" ::string(), % типи полів запису
               password ::string(),
               status :: user_status(),
               nickname ::string()}).

-spec(check_password(string(), #user{}) -> ok | {error, string()}). % декларація функції

check_password(Password, User) -> % визначення функції
    ...

Dialyzer (від англ. DIscrepancy AnaLYZer for ERlang Programs — «аналізатор суперечностей для Erlang-програм») програма для виявлення в коді окремих модулів і в окремих додатках, помилок типів, недосяжного коду та надлишкових перевірок. Усі виявлені Dialyzer дефекти вимагають усунення, бо інструмент не дає помилкових спрацьовувань. Для кожної функції всіх модулів, що перевіряються, Dialyzer, встановлює тип, використовуючи заснований на обмеженнях вивід типів і аналіз потоків даних. Після визначення типів функцій проводиться аналіз протиріч у програмі.[32]

Тестування, профілювання, рефакторинг

[ред. | ред. код]

Для модульного тестування Erlang надає EUnit. Для системного тестування Erlang надає фреймворк Common Test. EUnit містить засоби для опису тестів, в тому числі необхідний для цього набір макросів, а також виводить звіт по закінченню тестування. Тестування модулів відбувається шляхом підключення заголовного файлу з EUnit, а функції з тестами можуть бути як включені в сам модуль, що тестується, так і винесені в окремий модуль.[8]

Тестування паралельних програм можна виконати за допомогою Quviq Quick Check (версія Mini безкоштовно). Крім тестування, можна здійснити перевірку всіх можливих варіантів вихідних даних за допомогою методу перевірки моделей. Для цього є, створена в Мадридському політехнічному університеті, утиліта McErlang. [1][8]

Щоб профілювати код та виявити ступінь покриття коду тестами можна звернутися до модулів eprof, fprof, cover та утиліти cprof.[9]

Для Erlang розроблена низка інструментів рефакторингу вихідного коду (список не є вичерпним):

  • RefactorErl,
  • Wrangler,
  • автоматична, незалежна від IDE утиліта tidier.

Утиліта tidier дозволяє автоматично знаходити та виробляти еквівалентні перетворення коду, наприклад, замінює

lists:filter(fun (X) -> is_something(X) end, L)

на

[X || X <- L, is_something(X)]

[33]

Ефективність

[ред. | ред. код]

Erlang має свої секрети написання ефективного коду. Удосконалення мови робить деякі з трюків застарілими, тому документація є найкращим посібником у питаннях оптимізації, у сукупності з профілюванням та стрес-тестуванням.

Наприклад, при роботі зі списками не бажано додавати елемент до кінця довгого списку за допомогою конкатенації або функції додавання елемента до списку. Зате можна додати елемент до початку списку, а кінцевий результат обробити функцією звернення порядку елементів списку.

L1 = [New_Elem | List].
L2 = lists:reverse(L1).

Є свої рекомендації і для підвищення ефективності паралельних програм. До прикладу, дії, що вимагають багато пам'яті, найкраще виділяти в окремий процес, бо при цьому зменшуються витрати на збирання сміття. Тоді пам'ять швидше буде звільнено після завершення процесу.[8]

Erlang та інші мови програмування

[ред. | ред. код]

Інтеграція та гібридні мови

[ред. | ред. код]

Erlang-система дозволяє виконувати інтеграцію з системами на інших мовах програмування. Є механізми для мережевої взаємодії з С, Java, Лісп, Perl, Python, Ruby. До прикладу, для ефективнішого синхронного виклику невеликих функцій на C можна використовувати платформно-залежні функції (англ. NIF, natively implemented function). Високорівневі бібліотеки дозволяють Erlang-системі представляти C або Java-вузли як звичайні Erlang-вузли. Інші мови можна пов'язати з середовищем виконання Erlang за допомогою драйверів або мережевих сокетів за допомогою протоколів на кшталт HTTP, SNMP, IIOP. До прикладу, Ruby взаємодіє з Erlang за допомогою пакета erlectricity, а для Python розроблена реалізація Erlang-вузла у вигляді пакету py-interface.[20]

Віртуальна машина Erlang знаходить застосування і в інших мовах програмування. До прикладу, Elixir та проєкт Erl2 Джо Армстронга. Крім того, Роберт Вірдінг підтримує проєкт Lisp Flavored Erlang («Erlang, приправлений Ліспом»), у якому синтаксис Ліспа використовується з компілятором[34] Erlang. Є й інші BEAM-мови: Efene, Joxa, Reia[35], Luerl, Erlog[36].

На офіційному сайті мови є згадка проєкт про Erjang, в якому використовується віртуальна машина Java.

Порівняння Erlang та C++ за продуктивністю

[ред. | ред. код]

Практика показує, що для вивчених телекомунікаційних додатків код на Erlang був на 70—85 % коротшим, ніж на C++, а продуктивність системи при переписуванні коду з C++ на Erlang зросла майже на 100 %[37]. Для одного із використаних у дослідженні проєктів різниця була пояснена написанням додаткового C++ коду в рамках захисного програмування, управління пам'яттю та коду для високорівневої комунікації, тобто можливостями, які є частиною мови Erlang та бібліотек OTP.[8]

Порівняння взаємодії процесів у Erlang та Go

[ред. | ред. код]

Вплив теорії послідовних процесів, що взаємодіють, Чарльза Е.Хоара відчувається як у Go, так і в Erlang.

Erlang і Go

Спільне : процеси відправляють одне одному повідомлення.

Відмінне:

  • В Erlang це відбувається безпосередньо.
  • В Go за допомогою каналів (англ. channels)[38] Ці канали мають типи.[39]

Організація взаємодії з повідомленнями

У Erlang немає типізації часу компіляції за винятком охоронних виразів, що дозволяє посилати процесам повідомлення будь-якого типу. «Незрозуміле» повідомлення буде проігноровано, або назавжди залишиться в черзі[38].

Go дозволяє легко організувати групу «go-програм» (англ. goroutine — натяк на англ. co-routine — співпрограма) для отримання повідомлень з деякого каналу (такий підхід відомий як пул потоків).

Реалізація робочого пулу

В Erlang, при проєктуванні якого приділялася особлива увага детермінізму та часу затримки (англ. latency), реалізація робочого пулу можлива, але потребує додаткових зусиль. Численні відправники тривіально реалізуються в обох мовах.[38] Erlang-процес може надіслати повідомлення і чекати на нього відповідь (відповідно до деякого зразка), ігноруючи інші повідомлення в черзі.

В Go таке неможливо, але подібна функціональність може бути досягнута створенням (у тому числі динамічним) нових вводів, тобто поділом каналів за призначенням.[38]

Розділення між процесами

В Erlang відсутній стан, що розділяється між процесами (англ. shared mutable state) і тому ізольований процес дуже рідко представляє інтерес[38]

Go вимагає явної вказівки того, які go-програми будуть взаємодіяти з іншими передачею повідомлень.

Абстракції взаємодіючих процесів схожі в Erlang і Go. Щоб уникнути помилок при переході з однієї мови на іншу, слід враховувати тонкощі і особливості обох мов: шаблони, які хороші в одній мові, можуть не підходити для іншої.[38]

Критика

[ред. | ред. код]

В Erlang є недоліки[40]:

  • Недоліки синтаксису: символ закінчення виразу залежить від контексту (це або символи . , або ;). Це вимагає додаткової уваги при зміні місця виразу.
  • Надлишкове багатослів'я записів (record).
  • Завжди потрібно прописувати повний перелік альтернатив, або вказати останньою клозою true в if-виразі, для уникнення видачі винятку, якщо ні одна з умов не виконується.
  • Суворо обмежений набір функцій, які можна використовувати в if-виразах (цей недолік можна оминути використанням case-виразів).
  • Функціональний стиль і незмінювані змінні призводять у деяких додатках (наприклад, тести) до більшої кількості правок, ніж в інших мовах програмування, так як вставка деякої проміжної обробки може вимагати нових імен змінних, що може призвести до змін у коді, наступного далі за текстом.
  • З недоліків системи типів можна вказати відсутність рядкового типу, і неможливість динамічно додавати у записи нові члени.
  • Існують проблеми з організацією вихідного коду, яка можлива тільки через створення нового файлу.
  • Відсутність просторів імен, класів або інших засобів для організації коду.
  • Рівень якості модулів, за винятком основних, та документації є далеко не найкращим[40].

Джо Армстронг, один з творців мови, у своєму виступі на конференції з історії мов програмування в 2007 році перерахував список зон зростання для мови:[41]

  • Використання збирання сміття для атомів.
  • Поліпшення засобів сполучення із зовнішнім кодом (англ. foreign code).
  • Посилення ізоляції між процесами.
  • Більш виборча система безпеки серед вузлів Erlang, заснована на різному ступені довіри.
  • Окремі позначення для протоколів та систем.
  • Модулі мають бути об'єктами першого класу.

Масове поширення Erlang може стримувати незвичний для більшості програмістів синтаксис, використання функціональної парадигми, а також те, що найкраща на 2022 рік реалізація мови використовує віртуальну машину BEAM, а не більш поширену JVM.[42]

Сфера застосування

[ред. | ред. код]
Типова архітектура системи, яка використовує Erlang/OTP. Програми Erlang користуються службами Mnesia, SASL, агентами SNMP-моніторингу та іншими на базі фреймворку OTP, який у свою чергу використовує ERTS. Програми інших систем програмування підтримуються меншою мірою[43].

Erlang підходить для:

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

Erlang погано підходить для: Написання коду, що містить інтенсивні обчислення з плаваючою комою, який вимагає включення нативного коду конкретної платформи чи сильної оптимізації і для створення додатків, які потребують синхронного виконання завдань.

Для проєктів, в яких код повинен виконуватися на JVM або CLR, або проєктів, що вимагають безлічі бібліотек з інших систем програмування[44].

Крім того, Erlang став застосовуватися для :

  • Розробки систем хмари ще до того, як сформувалося саме поняття хмарних обчислень.[45]
  • Використовується в масштабних телекомунікаційних та Інтернет-додатках багатьма компаніями, включаючи Amazon EC2 з реалізацією SimpleDB.
  • Сервіс соціальних закладок Delicious, Facebook (бекенд для чату), зроблений на Erlang.
  • T-Mobile (сервіс SMS та системи аутентифікації)[46].
  • Серверне програмне забезпечення WhatsApp написане на Erlang.
  • У січні 2012 року сервери WhatsApp під FreeBSD з 96 ГБ оперативної пам'яті змогли обробляти від 1 до 2,8 мільйонів з'єднань[47][48].

Erlang часто ставлять у заслугу легендарну надійність ATM-комутатора AXD301 у мережі British Telecom.

За даними Ericsson, з січня 2002 року за декілька років трапилася лише одна незначна несправність, на підставі чого надійність системи згідно з розрахунками була 99,9999999 %[49]. Хоча більш реальні оцінки, що враховують багато інших факторів, говорять все-таки про «п'ять дев'яток», успіх маршрутизатора пов'язують з доступними засобами розробки надійних паралельних обчислень, вбудованими в Erlang[49].

Ще Erlang використовується в додатках з відкритим вихідним кодом, наприклад:

Для Erlang було написано декілька веб-серверів:

Ще були створені декілька веб-фреймворків і систем управління вмістом, таких як:

Серед відомого програмного забезпечення, написаного Erlang, яке не увійшло в категорії вище виділяють:

  • Розподілену NoSQL базу даних Riak, яка спроєктована за принципами Amazon DynamoDB[59].
  • Flussonic (раніше відомий як Erlyvideo) — відеострімінговий сервер, що підтримує декілька протоколів[60].

Крім того, написаний на Erlang інструмент Tsung, який дозволяє емулювати тисячі (за достатньої кількості тестових серверів — мільйони) одночасних користувачів, застосовується у стрес-тестуваннях розподілених систем[61].

Erlang підходить для завдань штучного інтелекту (особливо обчислювального інтелекту, нейроеволюції), заснованих на нейронних мережах. Таке застосування можливе завдяки п'ятьом ключовими властивостями «мови програмування нейронних мереж», що є у Erlang:

  • ізольовані процеси-нейрони (англ. encapsulation),
  • паралелізм (англ. concurrency, одночасність),
  • механізм виявлення збоїв,
  • незалежність від розташування (англ. location transparency)
  • гаряча заміна коду.

Прикладом такого застосування є реалізація одного з підходів до нейроеволюції — DXNN[62].

Спільнота

[ред. | ред. код]

Навколо технологій Erlang утворилась спільнота розробників, яка підтримує новачків.

Вихідний код Erlang доступний через сервіс спільної розробки GitHub. Розробники та користувачі Erlang можуть спілкуватися через список розсилки Erlang-questions (питання Erlang) або на IRC-каналі #erlang на Freenode.

Erlang Factory влаштовує у всьому світі заходи та конференції, серед яких конференція користувачів Erlang (Erlang User Conference).

Спеціальна група SIGPLAN ACM регулярно проводить Erlang-майстерню (Erlang Workshop), а конференція OSCON включає секцію з Erlang[63].

В Україні Erlang спільнота порівняно невелика.

Див. також

[ред. | ред. код]

Виноски

[ред. | ред. код]
  1. The erlang Open Source Project on Open Hub: Languages Page — 2006.
  2. Nordström, Bengt (1981). Programming in Constructive Set Theory. Proceedings of the 1981 conference on Functional programming languages and computer architecture - FPCA '81. ACM Press. doi:10.1145/800223.806773. Процитовано 16 серпня 2022.
  3. Начала работы с Erlang [Архівовано 2013-11-22 у Wayback Machine.] (рос.)
  4. а б Armstrong, Joe (2007). History of Erlang. HOPL III: Proceedings of the third ACM SIGPLAN conference on History of programming languages (англійська) . ISBN ISBN 978-1-59593-766-7. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  5. а б Erlang.org. http://erlang.org/course/history.html (англійська) .
  6. Däcker, Bjarne (2000). Concurrent Functional Programming for Telecommunications: A Case Study of Technology Introduction (англійська) . Royal Institute of Technology. с. 37.
  7. Fredlund, Lars-Åke (2012). Erlang – a platform for developing distributed software systems (англійська) . Madrid: Universidad Politécnica de Madrid.
  8. а б в г д е ж и к л м н п р с т у ф х ц ш щ ю я аа аб ав аг ад ае аж аи ак ал ам ан ап ар ас ат ау аф ах ац аш ащ аю ая Cesarini, Francesco (2009). Erlang Programming (англійська) . O’Reilly Media, Inc. с. 498. ISBN ISBN 978-0-596-51818-9.. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  9. а б в г д е ж и к л м н п р с т у ф х St. Laurent, Simon (2013). Introducing Erlang (англійська) . O’Reilly Media, Inc. с. 185. ISBN ISBN 978-1-449-33176-4.. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  10. Verraes, Mathias (9 грудня 2014). Let It Crash. Mathias Verraes' Blog (англ.). Процитовано 11 жовтня 2022.
  11. Tate, Bruce A (2010). Seven Languages in Seven Weeks: A Pragmatic Guide to Learning Programming Languages (англійська) . Pragmatic Bookshelf.
  12. а б Armstrong, Däcker, Lindgren, Millroth, Erlang product team at Ericsson. (2013). Open-source Erlang — White Paper. http://ftp.sunet.se/pub/lang/erlang/white_paper.html (англійська) . Ericsson AB. {{cite web}}: |archive-url= вимагає |archive-date= (довідка) [Архівовано 2011-10-25 у Wayback Machine.]
  13. а б в г д Hébert, Fred (2013). Learn You Some Erlang for Great Good!: A Beginner's Guide (англійська) . No Starch Press. с. 624. ISBN ISBN 978-1593274351.. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  14. а б math, STDLIB Reference Manual Version 1.19.3. https://www.erlang.org/doc/man/math.html (англійська) .
  15. Martin Logan, et al, 2011, с. 31—32.
  16. а б в г д е ж Erlang/OTP 25.0.4. https://www.erlang.org/doc/ (Англійська) . Процитовано 25 серпня 2022.
  17. а б в г д е ж и к Logan,Merritt, Carlsson., Martin, Eric, Richard (2011). Erlang and OTP in Action. Manning. с. 397. ISBN ISBN 9781933988788. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  18. Data Types, Erlang Reference Manual User's Guide Version 5.10.3. http://www.erlang.org/doc/reference_manual/data_types.html (англійська) . Ericsson AB. 2013.
  19. Ports and Port Drivers, Reference Manual User's Guide Version 5.10.3. https://web.archive.org/web/20131203022736/http://www.erlang.org/doc/reference_manual/ports.html (англійська) . Ericsson AB. 2013. Архів оригіналу за 3 грудня 2013. Процитовано 16 серпня 2022.
  20. а б Kessin, Zachary (2012). Building Web Applications with Erlang (Англійська) . O’Reilly Media, Inc. с. 156 p. ISBN ISBN 978-1-4493-0996-1.. {{cite book}}: Перевірте значення |isbn=: недійсний символ (довідка)
  21. Syntax in functions | Learn You Some Erlang for Great Good!. learnyousomeerlang.com. Процитовано 10 вересня 2022.
  22. The Preprocessor, Predefined Macros, Erlang Reference Manual User's Guide Version 5.10.3 (англійська) . Ericsson AB. 2013.
  23. Hebert, 2013, Modules.
  24. Martin Logan, et al, 2011, с. 75.
  25. The Erlang I/O-protocol, STDLIB User's Guide Version 1.19.4 (англійська) . Ericsson AB. 24 грудня 2013.
  26. а б STDLIB, STDLIB Reference Manual Version 1.19.3 (англ.). Ericsson AB. 2013. Архів оригіналу за 7 листопада 2013. Процитовано 1 грудня 2013.
  27. Erlang -- gen_server. www.erlang.org. Процитовано 16 вересня 2022.
  28. Erlang -- supervisor. www.erlang.org. Процитовано 16 вересня 2022.
  29. Erlang -- gen_statem. www.erlang.org. Процитовано 16 вересня 2022.
  30. application, Kernel Reference Manual Version 2.16.3. https://www.erlang.org/doc/apps/kernel/application.html (англійська) . Ericsson AB. 1 листопада 2013. Процитовано 2013.
  31. Klas Eriksson, M. Williams, J. Armstrong (2 грудня 2013). . Programming Rules and Conventions. http://erlang.se/doc/programming_rules.shtml (англійська) . Ericsson AB. Архів оригіналу за 30 листопада 2013. Процитовано 16 серпня 2022. [Архівовано 2013-11-30 у Wayback Machine.]
  32. Aronis, Stavros and Papaspyrou, Nikolaos and Roukounaki, Katerina and Sagonas, Konstantinos and Tsiouris, Yiannis and Venetis, Ioannis E. (2012). A Scalability Benchmark Suite for Erlang/OTP (англійська) . Copenhagen, Denmark: ACM.: Proceedings of the Eleventh ACM SIGPLAN Workshop on Erlang Workshop. Erlang '12. с. pp. 33—42. ISBN 978-1-59593-766-7.. {{cite book}}: |pages= має зайвий текст (довідка); Перевірте значення |isbn=: недійсний символ (довідка)
  33. Avgerinos, Thanassis and Sagonas, Konstantinos. (2009). Cleaning Up Erlang Code is a Dirty Job but Somebody's Gotta Do It (англійська) . Edinburgh, Scotland:: Proceedings of the 8th ACM SIGPLAN Workshop on ERLANG ACM,. {{cite book}}: Недійсний |deadurl=https://dl.acm.org/doi/10.1145/1596600.1596602 (довідка)
  34. Lisp Flavored Erlang. https://lfe.github.io/ (англійська) .
  35. Why I’m stopping work on Reia. http://www.unlimitednovelty.com/2011/06/why-im-stopping-work-on-reia.html (англійська) . JUNE 29, 2011.
  36. Federico Carrone, et al Spawned Shelter!. http://spawnedshelter.com/ (Англійська) . 15 вересня 2013.
  37. Nyström, Trinder, King, 2008.
  38. а б в г д е David Chisnall (14 листопада 2011). A Tale of Two Concurrency Models: Comparing the Go and Erlang Programming Languages (англ.). Pearson Education, Informit. Архів оригіналу за 3 грудня 2013. Процитовано 1 грудня 2013.
  39. Chisnall, David (14 листопада 2011). A Tale of Two Concurrency Models: Comparing the Go and Erlang Programming Languages. https://www.informit.com/articles/ (англійська) . Процитовано 1 грудня 2013.
  40. а б Katz, Damien (9 березня 2008). What Sucks About Erlang. http://damienkatz.net/2008/03/what_sucks_about.html (англ.). Архів оригіналу за 5 грудня 2013. Процитовано 24 листопада 2013.
  41. Armstrong, A History of Erlang, 2007, 6-19,6-20.
  42. Tate, 2010, с. 221.
  43. Dan McCreary, Ann Kelly, 2013.
  44. Larson, Erlang for Concurrent Programming, 2008.
  45. Робота з великими обсягами даних в хмарі за допомогою MapReduce (англійська) . IBM. 1 березня 2012. Архів оригіналу за 11 грудня 2013. Процитовано 7 грудня 2013.
  46. Ericsson та 4 December 2014, "Ericsson". Ericsson.com. 4 December 2014. Retrieved 7 April 2018..
  47. Scaling to Millions of Simultaneous Connections Архівна копія на сайті Wayback Machine., Rick Reed (WhatsApp) // Erlang Factory SF, March 30, 2012
  48. 1 million is so 2011 Архівна копія на сайті Wayback Machine. // WhatsApp Blog, January 6, 2012
  49. а б Томпсон, 2012, с. 33.
  50. http://discoproject.org/ (1 березня 2012). Робота з великими обсягами даних в хмарі за допомогою Map Reduce. http://discoproject.org/ (Англійська) . Архів оригіналу за 11 грудня 2013. Процитовано 7 грудня 2013.
  51. Disco (сторінка проєкту). http://discoproject.org/. Архів оригіналу за 16 вересня 2010. Процитовано 7 грудня 2013.
  52. Pivotal Software, Inc. RabbitMQ MQTT Adapter. Архів оригіналу за 1 грудня 2015. Процитовано 10 грудня 2015.
  53. Томпсон, 2012, с. 23—25.
  54. Cowboy, Nine Nines, 15 вересня 2022, процитовано 16 вересня 2022
  55. Martin Brown (2013). Introduction to programming in Erlang, Part 2: Use advanced features and functionality (англ.). IBM. Архів оригіналу за 3 грудня 2013. Процитовано 1 грудня 2013.
  56. N2O. Архів оригіналу за 18 серпня 2015. Процитовано 13 грудня 2014.
  57. Zachary Kessin, 2012, с. 121—123.
  58. Novaframework. https://github.com/novaframework/nova (англійська) . 24 травня 2022. Процитовано 22.08.2022.
  59. Simon St. Laurent, 2013, с. 167.
  60. Flussonic — live streaming software. Архів оригіналу за 16 червня 2012. Процитовано 7 травня 2012.
  61. Bradley Holt. Chapter 6. Distributed Load Testing // Scaling CouchDB. — O'Reilly Media, Inc, 2011. — P. 39. — ISBN 978-1-4493-0343-3.
  62. Sher, 2013, с. 144—150, Chapter 5 The Unintentional Neural Network Programming Language.
  63. Simon St. Laurent, 2013, с. 168—169.

Посилання

[ред. | ред. код]