Полное руководство по Erlang с примерами кода: от основ к конкурентности

Комплексное руководство по языку программирования Erlang и платформе OTP. Объясняются основы синтаксиса, функционального подхода и работы со списками. Детально разбирается модель акторов для конкурентности с примерами кода процессов, а также введение в OTP-библиотеки (GenServer, Supervisor) для создания отказоустойчивых систем.
Erlang — это не просто язык программирования, а целая экосистема, созданная компанией Ericsson для разработки отказоустойчивых, распределенных, высоконагруженных систем с «горячим» обновлением кода. Если ваше приложение должно работать 99.9999999% времени (девять девяток) и масштабироваться на сотни узлов, то Erlang/OTP — это ваш выбор. Это руководство проведет вас от базовых синтаксических конструкций к пониманию философии конкурентности, которая является сердцем Erlang, с наглядными примерами кода.

Начнем с основ. Синтаксис Erlang может показаться необычным для тех, кто пришел с языков вроде C++ или Java. Он функциональный, с динамической типизацией и строгой иммутабельностью данных. Переменные начинаются с заглавной буквы, а атомы (константы-метки) — со строчной или в одинарных кавычках. Каждое выражение заканчивается точкой.

Вот простой модуль с функцией вычисления факториала:
-module(math_stuff).
-export([factorial/1]).

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

Здесь мы видим несколько ключевых моментов: объявление модуля, экспорт функции (указана ее арность — /1), сопоставление с образцом (pattern matching) и охрану (guard) `when N > 0`. Рекурсия — стандартный способ организации циклов в функциональных языках.

Работа со списками и рекурсия — основа основ. Функция для подсчета суммы элементов списка:
sum_list([]) -> 0;
sum_list([Head | Tail]) -> Head + sum_list(Tail).

`[Head | Tail]` — это pattern matching на голову и хвост списка. Но чаще используют встроенные функции из модуля `lists`, например, `lists:sum/1`.

Однако истинная мощь Erlang раскрывается в его модели конкурентности, основанной на акторах. Вместо общих областей памяти и потоков (threads) Erlang использует легковесные процессы (не процессы ОС!), которые изолированы, общаются только через асинхронную передачу сообщений и имеют собственный сборщик мусора. Это делает параллелизм в Erlang предсказуемым и отказоустойчивым.

Создадим простой процесс-калькулятор:
-module(calc).
-export([start/0, loop/0]).

start() ->
 Pid = spawn(calc, loop, []), % Порождение нового процесса
 Pid ! {self(), {add, 5, 3}}, % Отправка сообщения. self() - PID отправителя
 receive
 {Pid, Result} -> % Получение ответа
 io:format("Result: ~p~n", [Result])
 after 2000 ->
 io:format("Timeout!~n")
 end.

loop() ->
 receive % Цикл получения сообщений
 {From, {add, A, B}} ->
 From ! {self(), A + B}, % Отправка ответа отправителю
 loop();
 {From, {multiply, A, B}} ->
 From ! {self(), A * B},
 loop();
 stop ->
 io:format("Calculator stopped.~n");
 _ ->
 io:format("Unknown command.~n"),
 loop()
 end.

Здесь `spawn/3` создает новый процесс, выполняющий функцию `loop/0`. Оператор `!` (bang) отправляет сообщение в почтовый ящик процесса. `receive ... end` извлекает и обрабатывает сообщения по образцу. Это ядро всей модели акторов.

Но писать такие циклы приема сообщений для каждого сервиса утомительно. На помощь приходит OTP (Open Telecom Platform) — набор библиотек и паттернов, которые абстрагируют рутину. Самые важные поведения (behaviours) — это GenServer (универсальный сервер), Supervisor (надзор за процессами) и Application.

Вот упрощенный GenServer для того же калькулятора:
-module(calc_otp).
-behaviour(gen_server).
-export([start_link/0, add/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

start_link() ->
 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). % Регистрация под именем

add(A, B) ->
 gen_server:call(?MODULE, {add, A, B}). % Синхронный вызов

init([]) ->
 {ok, #{}}. % Состояние - пустая карта

handle_call({add, A, B}, _From, State) ->
 Result = A + B,
 {reply, Result, State}; % Возвращаем ответ и неизмененное состояние
handle_call(_Request, _From, State) ->
 {reply, {error, unknown_call}, State}.

% Остальные callback-функции (handle_cast, handle_info) для асинхронных сообщений и системных
% ...

GenServer берет на себя управление циклом приема сообщений, таймаутами, системными сообщениями. Разработчик лишь заполняет callback-функции. Supervisor же позволяет создавать иерархии процессов с заданными стратегиями перезапуска (например, один упал — перезапусти только его; все упали — перезапусти всех). Это основа для построения отказоустойчивых систем, где падение одного компонента не приводит к краху всего приложения.

Обработка ошибок в Erlang следует философии «пусть упадет» (let it crash). Вместо того чтобы писать код, перегруженный проверками на каждую возможную ошибку, вы создаете изолированные процессы-работники и надежных супервизоров, которые их перезапустят. Это приводит к более чистому и надежному коду.

Работа с бинарными данями — еще один конек Erlang, критичный для сетевых протоколов. Синтаксис для pattern matching на бинарники мощный и лаконичный:
parse_packet() ->
 {ok, {Type, Payload}, Rest}.

Этот один шаблон извлекает тип (1 байт), длину (2 байта), payload указанной длины и остаток бинарных данных.

В заключение, изучение Erlang — это инвестиция в парадигму построения систем, для которых надежность и параллелизм являются ключевыми требованиями. Начните с основ функционального программирования и pattern matching, затем глубоко погрузитесь в модель акторов и, наконец, освойте магию OTP-библиотек. Это откроет вам путь к созданию систем, которые, как говорят в сообществе, «никогда не останавливаются».
331 1

Комментарии (8)

avatar
8jx5655rs 27.03.2026
Мне не хватило примеров с реальными проектами. Теория хороша, но как это работает в продакшене?
avatar
t1ol6s1 27.03.2026
Слишком сложно для новичков. Первые примеры кода могли бы быть проще, без сразу нескольких концепций.
avatar
tn6q58wwa 27.03.2026
Как инженер телекома, подтверждаю: Erlang незаменим для систем, где простои недопустимы.
avatar
u4467txw 28.03.2026
После Go и Akka вернулся к истокам. Конкурентная модель Erlang до сих пор поражает своей элегантностью.
avatar
kyicbcvndvy 29.03.2026
Интересно, но статья не раскрывает минусы. Разработка на Erlang медленнее, а сообщество меньше, чем у других языков.
avatar
vtsluo2fri 29.03.2026
Спасибо за акцент на философии языка! Понимание «let it crash» меняет подход к разработке.
avatar
7ne7in8ylkco 29.03.2026
Отличное руководство! Как раз искал структурированный материал по конкурентности в Erlang. Жду продолжения про OTP.
avatar
plunh6ivq7 30.03.2026
Хорошо, но Elixir сейчас популярнее. Может, сделаете сравнение или руководство по нему?
Вы просмотрели все комментарии