LEARN X · ЗА 17 МИН

Erlang

Весь Erlang на одной странице: модули, сопоставление с образцом, рекурсия, процессы, receive и let it crash — экспресс-тур в комментариях кода.

Erlang — функциональный язык для конкурентных и отказоустойчивых систем. Лёгкие процессы, обмен сообщениями, неизменяемые данные и философия «let it crash». Весь язык — на одной странице, прямо в комментариях кода.

Модули и вывод

Каждый файл — модуль. Имя модуля совпадает с именем файла (hello.erl).

% Это однострочный комментарий — начинается с %

% Объявление модуля (обязательно, имя = имя файла без .erl)
-module(hello).

% Экспорт функций наружу: имя/арность (число аргументов)
-export([start/0]).

start() ->
    % io:format печатает в консоль. ~p — «умный» вывод любого терма,
    % ~s — строка, ~w — сырой терм, ~n — перевод строки
    io:format("Привет, Erlang!~n"),
    io:format("Число: ~p, строка: ~s~n", [42, "hi"]).

% Компиляция и запуск в оболочке erl:
%   c(hello).        — компилировать
%   hello:start().   — вызвать функцию модуля

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

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

% Целые числа произвольной точности
Big = 123456789012345678901234567890.
Hex = 16#FF.        % = 255, основание#число
Bin = 2#1010.       % = 10 (двоичное)
Char = $A.          % = 65 (код символа)

% Числа с плавающей точкой
Pi = 3.14159.

% Атомы — именованные константы (с маленькой буквы или в '')
Status = ok.
Color = 'красный цвет'.   % атом в кавычках, если есть пробелы
Flag = true.             % true/false — это атомы, не отдельный тип

% Переменные — с БОЛЬШОЙ буквы, связываются ОДИН раз
X = 10.
% X = 20.   % ОШИБКА: переменная уже связана (неизменяемость)

_Ignored = 5.   % имя с _ или одиночное _ — «мне всё равно»

% Узнать тип:
% is_integer(X), is_atom(Status), is_float(Pi), is_list(...)

Сопоставление с образцом

Знак = — это не присваивание, а match: связать переменные так, чтобы стороны совпали.

% = пытается сопоставить левую и правую части
X = 1.              % X свободна -> связывается с 1
1 = X.              % обе стороны = 1 -> ок (ничего не делает)
% 2 = X.            % ОШИБКА: 2 не совпадает с 1

% Разбор структуры на части
{Name, Age} = {"Аня", 25}.   % Name = "Аня", Age = 25
[Head | Tail] = [1, 2, 3].    % Head = 1, Tail = [2, 3]

% Можно вкладывать образцы и фиксировать значения
{ok, Result} = {ok, 42}.      % Result = 42
% {ok, R} = {error, oops}.    % ОШИБКА: атом error != ok

% _ совпадает с чем угодно и ничего не связывает
{_, Second, _} = {a, b, c}.   % Second = b

Списки и кортежи

% Список — упорядоченная цепочка, любой длины и типов
List = [1, 2, 3, atom, "строка"].

% Конструктор «голова | хвост»
L = [1, 2, 3].
NewL = [0 | L].          % [0, 1, 2, 3] — добавили в начало (дёшево)
[H | T] = L.             % H = 1, T = [2, 3]

% Объединение и вычитание списков
[1, 2] ++ [3, 4].        % [1, 2, 3, 4]
[1, 2, 3, 2] -- [2].     % [1, 3, 2] — убирает первое вхождение

length([1, 2, 3]).       % 3
hd([1, 2, 3]).           % 1  (голова)
tl([1, 2, 3]).           % [2, 3]  (хвост)

% Кортеж {} — фиксированный набор полей (как запись без имён)
Point = {point, 10, 20}.   % атом-тег в начале — частая идиома
element(2, Point).         % 10
setelement(2, Point, 99).  % {point, 99, 20} (новый кортеж)

Строки и бинарники

% "Строка" в Erlang — это СПИСОК кодов символов
S = "ABC".
S = [65, 66, 67].        % это одно и то же!

% Операции из модуля string
string:to_upper("hi").       % "HI"
string:tokens("a,b,c", ","). % ["a", "b", "c"]
string:length("Привет").     % 6

% Бинарники << ... >> — компактные байтовые данные (эффективнее списков)
B = <<"hello">>.
Bytes = <<1, 2, 3>>.

% Сопоставление по бинарнику: разбор байтов
<<A, Rest/binary>> = <<10, 20, 30>>.   % A = 10, Rest = <<20, 30>>

% Для текста на проде обычно берут binary, а не списки символов
binary_to_list(<<"abc">>).   % "abc" -> [97, 98, 99]
list_to_binary("abc").       % <<"abc">>

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

% Арифметика
5 + 3.    5 - 3.    5 * 3.    7 / 2.     % 3.5 (всегда float)
7 div 2.  % 3 (целочисленное деление)
7 rem 2.  % 1 (остаток)

% Сравнение: =:= строгое равенство, == с приведением
1 =:= 1.    % true
1 == 1.0.   % true  (== не различает int/float)
1 =:= 1.0.  % false (=:= различает типы)
1 =/= 2.    % true  (строгое неравенство)

% Логика: andalso/orelse — ленивые, and/or — нет
true andalso false.   % false
(1 < 2) orelse (3 > 9). % true

% case — основной выбор по образцу
classify(N) ->
    case N of
        0          -> zero;
        X when X > 0 -> positive;   % when — это охранное условие (guard)
        _          -> negative
    end.

% if — выбор по guard-выражениям (без значения для сравнения)
sign(N) ->
    if
        N > 0  -> pos;
        N < 0  -> neg;
        true   -> zero   % true — как else, обязателен
    end.

Функции

Циклов нет — повторение делается рекурсией. Функция может иметь несколько клауз с разными образцами.

% Несколько клауз: выбирается первая подходящая по образцу/guard
fact(0) -> 1;                       % базовый случай, ; разделяет клаузы
fact(N) when N > 0 -> N * fact(N - 1).  % рекурсивный, . завершает функцию

% Рекурсия по списку через [H | T]
sum([]) -> 0;                       % пустой список -> 0
sum([H | T]) -> H + sum(T).          % голова + сумма хвоста

% Хвостовая рекурсия (аккумулятор) — не растёт стек, так пишут циклы
sum_tail(List) -> sum_tail(List, 0).
sum_tail([], Acc) -> Acc;
sum_tail([H | T], Acc) -> sum_tail(T, Acc + H).

% Вызов: fact(5) -> 120,  sum([1,2,3]) -> 6

Функции высшего порядка

fun — анонимная функция; модуль lists даёт map/filter/foldl.

% Анонимная функция (лямбда)
Double = fun(X) -> X * 2 end.
Double(21).                       % 42

% map — применить функцию к каждому элементу
lists:map(fun(X) -> X * X end, [1, 2, 3]).   % [1, 4, 9]

% filter — оставить подходящие
lists:filter(fun(X) -> X rem 2 =:= 0 end, [1, 2, 3, 4]).  % [2, 4]

% foldl — свёртка слева с аккумулятором
lists:foldl(fun(X, Acc) -> X + Acc end, 0, [1, 2, 3, 4]).  % 10

% Передача именованной функции по ссылке: fun Модуль:Имя/Арность
lists:map(fun erlang:abs/1, [-1, -2, 3]).   % [1, 2, 3]

% Замыкание захватывает переменные окружения
Mul = fun(N) -> fun(X) -> X * N end end.
(Mul(3))(10).                     % 30

Записи (records)

Запись — именованный кортеж с доступом по полям.

% Объявление записи (обычно в .hrl-файле)
-record(user, {name, age = 0, email}).   % age по умолчанию = 0

% Создание
U = #user{name = "Аня", age = 25}.
% email не указан -> undefined, age по умолчанию переопределён на 25

% Доступ к полю: Запись#имя.поле
U#user.name.                      % "Аня"

% Обновление (создаётся новая запись, старая не меняется)
U2 = U#user{age = 26}.

% Сопоставление по записи в образце
greet(#user{name = N}) ->
    io:format("Привет, ~s!~n", [N]).

Обработка списков (генераторы)

List comprehension: [Выражение || Генератор, Фильтр].

% Квадраты чисел: X <- L читается «X берётся из L»
[X * X || X <- [1, 2, 3, 4]].          % [1, 4, 9, 16]

% С фильтром (guard после запятой)
[X || X <- [1, 2, 3, 4, 5], X rem 2 =:= 0].   % [2, 4]

% Несколько генераторов — декартово произведение
[{X, Y} || X <- [1, 2], Y <- [a, b]].
% [{1,a}, {1,b}, {2,a}, {2,b}]

% Преобразование с условием и образцом
Pairs = [{ok, 1}, {error, 2}, {ok, 3}].
[V || {ok, V} <- Pairs].               % [1, 3] — берём только ok-кортежи

Процессы и сообщения

Главная фишка Erlang: тысячи лёгких процессов общаются через ! и receive. Памяти не разделяют.

% spawn запускает функцию в НОВОМ процессе, возвращает его Pid
Pid = spawn(fun() -> loop() end).

% Отправка сообщения: Pid ! Сообщение (асинхронно, не ждёт ответа)
Pid ! {hello, "мир"}.

% Процесс принимает сообщения через receive (сопоставление с образцом)
loop() ->
    receive
        {hello, Text} ->
            io:format("Получил: ~s~n", [Text]),
            loop();                     % рекурсия = вечный цикл ожидания
        {add, A, B, From} ->
            From ! {result, A + B},      % ответ отправителю по его Pid
            loop();
        stop ->
            io:format("Останавливаюсь~n")  % не зовём loop() -> процесс завершён
    after 5000 ->
        io:format("Тишина 5 секунд~n")   % таймаут ожидания (мс)
    end.

% self() — Pid текущего процесса (чтобы получить ответ)
Pid ! {add, 2, 3, self()},
receive {result, R} -> R end.            % R = 5

Обработка ошибок: let it crash

Философия Erlang: не страхуйся от всего, дай процессу упасть, а супервизор перезапустит.

% try/catch ловит три класса: throw, error, exit
safe_div(A, B) ->
    try A / B of
        Result -> {ok, Result}
    catch
        error:badarith -> {error, division_by_zero};
        Class:Reason   -> {error, {Class, Reason}}
    end.

safe_div(10, 2).   % {ok, 5.0}
safe_div(10, 0).   % {error, division_by_zero}

% Бросить исключение вручную
throw(my_error).
error(badarg).
exit(normal).

% «Let it crash»: процессы связывают через link/монитор.
% Связанный процесс падает -> сигнал уходит супервизору,
% который перезапускает упавшего в чистом состоянии.
spawn_link(fun() -> risky() end).

% Идиома {ok, V} | {error, Reason} часто заменяет исключения:
case file:read_file("data.txt") of
    {ok, Bin}      -> process(Bin);
    {error, Reason} -> io:format("Ошибка: ~p~n", [Reason])
end.
Поддержать проект