Основи Erlang

Перші кроки для тих, хто хоче почати вивчати Erlang

Модулі та функції

В Erlang-зі програми будуються з функцій, які викликають одна одну. Функції, у свою чергу, групуються і визначаються всередині модулів. Вихідний код модулів Erlang зберігається у файлах з розширенням .erl, при цьому ім’я модуля має бути таким самим, як і ім’я файлу (без розширення). Перед тим як запустити модуль його потрібно скомпілювати. Скомпільовані модулі зберігаються у файлах з розширенням .beam.

Визначення функції складається із заголовка і тіла функції. Заголовок функції складається з імені функції, яке є атомом, за яким у дужках йдуть формальні параметри функції. Кількість параметрів функції називається арністю (arity). Функції в Erlang унікально визначаються ім’ям модуля, ім’ям функції і арністю, тобто функції, що знаходяться в одному модулі з однаковими іменами, але з різною арністю є різними функціями. Стрілка “->” відокремлює заголовок функції від її тіла.

Створимо файл з ім’ям geometry.erl:

-module (geometry).
-export ([area / 1]).

% Функція для обчислення площі
area ((square, Side)) ->
    Side * Side;
area ((rectangle, Width, Height)) ->
    Width * Height;
area ((circle, Radius)) ->
    3.1415926 * Radius * Radius.

На початку модуля знаходяться директиви модуля в такому форматі -директива (значення). Директива module описує ім’я модуля, яка повинна збігатися з ім’ям файлу (без розширення). Директива export описує експортовані функції (які будуть доступні ззовні модуля) у вигляді списку у форматі ім’я/арність. У даному випадку модуль називається geometry і експортує одну функцію area з одним аргументом. Зауважте, що кожна директива закінчується крапкою. Рядки, що починаються зі знаку %.

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

Переходимо в оболонку Erlang-а:

1> c (geometry).
(ok, geometry)
2> geometry: area ((circle, 20)).
1256.63704
3> geometry: area ((square, 20)).
400
4> geometry: area ((rectangle, 10, 20)).
200
5> geometry: area ((triangle, 10, 20, 30)).
** Exception error: no function clause matching 
geometry:area ((triangle, 10,20,30))

У першому рядку ми використовували функцію c(), визначену в оболонці для компіляції нашого модуля. Ця функція повертає кортеж (ok, geometry), що говорить про успішну компіляції модуля. Поза оболонки модуль може бути скомпільовано за допомогою утиліти erlc.

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

Інший приклад

Тепер розглянемо більш складний приклад з використанням введення/виводу і рекурсії:

-module (persons).
-export ([person_list / 1]).

person_list (Persons) ->
    person_list (Persons, 0).

person_list ([(person, FirstName, LastName) | Persons], N) ->
    io: format ( "~s ~s ~n", [FirstName, LastName]),
    person_list (Persons, N + 1);
person_list ([], N) ->
    io: format ( "Total: ~p ~n", [N]).

Новий модуль називається persons і експортує функцію person_list/1 (з одним аргументом). Зауважте, що в модулі так само є функція person_list/2 (з двома аргументами), але в даному випадку вона буде використана тільки всередині модуля. Функція person_list/1 викликає допоміжну функцію person_list/2.

У person_list/2 необхідно передати два аргументи: список користувачів і початкове значення для аргументу-лічильника. Функція person_list/2 складається з двох пропозицій. У першому вислові функції ми відокремлюємо перший елемент списку користувачів (зауважте, що ми відокремлюємо ім’я та прізвище прямо в шаблоні аргументу). Потім використовується функцію format з бібліотечного модуля io, що б вивести ім’я та прізвище користувача на екран, і після цього ми викликаємо (рекурсивно) person_list/2 з рештою користувачами і збільшеним лічильником користувачів.

Бібліотечної функції io:format/2 потрібно передати два аргументи – формат виводу і список аргументів. У даному випадку формат виводу складається з двох шаблонів для виведення рядків ~s і переведення рядка ~n. Модуль io містить велику кількість функцій для роботи із стандартним вводом/виводом.

Друга пропозиція функції print_list/2 викликається, коли список користувачів виявляється порожнім (це відбувається при закінченні виведення користувачів) і виводиться загальна кількість виведених імен користувачів з використанням аргументу-лічильника. У другому вислові ми так само використовуємо новий шаблон для io:format/2 – ~p, що виводить аргумент у форматі в якому це робить оболонка.

1> c (persons).
(ok, persons)
2> persons: person_list ([]).
Total: 0
ok
3> persons: person_list ([(person, "Joe", "Armstrong "}]).
Joe Armstrong
Total: 1
ok
4> persons: person_list ([(person, "Joe", "Armstrong"),
4> (person, "Mike", "Williams"),
4> (person, "Robert", "Virding "}]).
Joe Armstrong
Mike Williams
Robert Virding
Total: 3
ok

Обмежувачі

Часто порівняння з шаблоном для функцій буває недостатньо і тут на допомогу приходять обмежувачі (guards), які дозволяють використовувати прості тести та порівняння змінних разом з шаблонами. Окрім функцій, обмежувачі можна використовувати в деяких інших конструкціях умовного виконання, наприклад, у конструкції case. Для функцій обмежувачі повинні бути розташовані перед символами ->, що розділяють заголовок і тіло функції. Наприклад, можна написати функцію для знаходження максимального значення таким чином:

max (X, Y) when X> Y ->
    X;
max (_X, Y) ->
    Y.

У першому вислові функції використовуються обмежувачі, які починаються зі слова when. Перша пропозиція виконується тільки у випадку якщо X > Y, інакше виконується друга пропозиція. У другому вислові перша змінна називається _X – використання підкреслення на початку імені змінної дозволяє уникнути попередження про те, що змінна не використовується, хоча цим треба користуватися з обережністю, щоб не пропустити помилкові ситуації.

Обмежувачі являють собою або один умовний вираз, який повертає true/false, або можуть бути записані як складовий вираз наступним чином:

  • Послідовність обмежувачів розділених крапкою з комою; істинна, якщо хоча б один з обмежувачів у послідовності повертає true;
  • Послідовність обмежувачів розділених комою, істинна тільки, якщо всі обмежувачі в послідовності повертають true;

Не всі вирази доступні для використання в якості обмежувачів для уникнення можливих побічних ефектів. Ось список доступних виразів:

  • Атом true (істина);
  • Різні константи і змінні. В обмежувач всі вони являють собою помилкові значення;
  • Функції для тестування типів даних і деякі вбудовані функції, наприклад: is_atom, is_boolean, is_tuple, size та ін;
  • Порівняння термінів, наприклад =: =, = / =, <,> тощо;
  • Арифметичні операції;
  • Булевські операції;
  • Булевські операції з короткою схемою обчислення (short-circuit);

Умовне виконання

У Erlang є три форми умовного виконання, які в більшості випадків можуть бути взаємозамінні. Конструкції case та if.

У конструкції case спочатку виконується вираз між case і of, і потім результат послідовно порівнюється з шаблонами. Разом з шаблонами так само можна використовувати і обмежувачі.

case is_boolean (Variable) of
    true ->
        1;
    false ->
        0
end

У цьому прикладі в якості вираження case … of виконується функція is_boolean і шаблонами слугують true і false. Два вислови розділені крапкою з комою, і конструкція закінчується ключовим словом end. У випадку, якщо шаблон не буде знайдений, то виникне експешн.

Конструкція if використовує тільки обмежувачі, які послідовно виконуються, поки не буде отримано значення істина:

if
    X> Y ->
        true;
    true ->
        false
end

У даному випадку обмежувач true діє як конструкція «інакше» в інших мовах, тобто значенням if буде false якщо X = < Y. У випадку якщо жоден з обмежувачів не дасть значення істина то виникне експешн. Анонімні функції

Анонімні функції визначаються з ключовим словом fun і схожі на визначення звичайних функцій за винятком відсутності імені.

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

times (N) ->
    fun
        (X) when is_number (X) ->
            X * N;
        (_) ->
            erlang: error (number_expected)
    end.

Тут функція times є функцією вищого порядку, тому що повертає іншу функцію. Визначення анонімної функції між ключовими словами fun і end складається з двох пропозицій. У першому вислові за допомогою обмежувача з функцією is_number ми визначаємо, що передано число (число може бути цілим, або речовим) і множимо його на аргумент, переданий в основну функцію при створенні нашої анонімної функції. У другому вислові ми трохи забігаємо вперед і використовуємо генерацію винятків. Крім цього шаблон другого виразу використовує знак підкреслення, що говорить, що нам абсолютно не важлива ця змінна.

Перевірка:

1> c (times).
(ok, times)
2> N2 = times:times (2).
# Fun 
3> N2 (4).
8
4> N10 = times:times (10).
# Fun 
5> N10 (4).
40

У рядку 2 ми використовуємо функцію times:times для отримання функції яка примножує значення на 2 і в рядку 4 створюється функція, які примножує значення на 10.

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

6> Double = times:times (2).
# Fun 
7> lists:map(Double, [1, 2, 3, 4]).
[2,4,6,8] 

Обробка винятків

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

Виключення в своєму коді можна створити, використовуючи одну із вбудованих функцій:

  • Exit (Why) – ця функція використовується, коли потрібно дійсно перервати виконання поточного процесу. Якщо цей виняток не перехоплюється, то всім процесам, приєднаним до цього, надсилається повідомлення ( ‘EXIT’, Pid, Why).
  • Throw (Why) – ця функція використовується для генерації виключення, яке викликає сторона, швидше за все, захоче перехопити. Таким чином, ми документуємо, що наша функція може генерувати цей виняток. У більшості випадків рекомендується не викидати це виняток за межі модуля.
  • Erlang:error (Why) – ця функція використовується для аварійних ситуацій, що не очікує викликаюча сторона.

У Erlang існує два способи обробки виключень – вираз catch і конструкція try/catch.

1> catch 2 + 2.
4
2> catch 2 + a.
( 'EXIT', (badarith, [(erlang ,'+',[ 2, a]),
                   (erl_eval, do_apply, 5),
                   (erl_eval, expr, 5),
                   (shell, exprs, 6),
                   (shell, eval_exprs, 6),
                   (shell, eval_loop, 3)]))
3> catch exit ( "Exit").
( 'EXIT', "Exit")
4> catch throw ( "Throw").
"Throw"
5> catch erlang: error ( "Error").
( 'EXIT', ( "Error",
         [(erl_eval, do_apply, 5),
          (erl_eval, expr, 5),
          (shell, exprs, 6),
          (shell, eval_exprs, 6),
          (shell, eval_loop, 3)]))

У першому рядку ми пробуємо catch з виразом 2 + 2, яке успішно виконується, повертаючи 4. У другому рядку робиться спроба скласти ціле і атом і catch повертає опис помилки у вигляді ( ‘EXIT’, (помилка, стек викликів)). Наступні три рядки показують що повертаються значення в залежності від способу створення виключень. Часто catch використовують спільно з конструкцією case для обробки помилок у висловах.

Конструкція try/catch дозволяє обробляти тільки необхідні для обробки типи помилок і навіть може бути поєднана з конструкцією схожою на case.

try 2 + a of
    Value ->
        ok
catch
    error: _ ->
        error
end.

У цьому прикладі ми намагаємося виконати вираз 2 + a й шаблони між of … catch відповідають шаблонам у виразі case. Шаблони між catch … end (в яких так само можна використовувати обмежувачі) використовуються для зіставлення з помилками, де помилка описується як тип:значення.

Бібліотечні модулі

До складу Erlang включено велику кількість стандартних бібліотечних модулів. Детальний опис модулів можна знайти за наступним посиланням: http://erlang.org/doc/man_index.html. Нижче описуються найбільш корисні модулі:

  • Erlang – модуль, який містить більшість вбудованих функції Erlang. Більшість функцій з цього модуля доступні без зазначення імені модуля, але до решти потрібно звертатися тільки по повному імені, із зазначенням модуля;
  • File – інтерфейс до файлової системи, що містить функції для роботи з файлами;
  • Io – інтерфейс стандартного введення/виводу. Має функції для читання/запису файлів, у тому числі для стандартних пристроїв введення/виводу, форматування;
  • Lists – містить функції для роботи зі списками;
  • Math – модуль, що містить стандартні математичні функції;
  • String – містить функції для роботи з рядками;



coded by nessus