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-библиотек. Это откроет вам путь к созданию систем, которые, как говорят в сообществе, «никогда не останавливаются».
Полное руководство по Erlang с примерами кода: от основ к конкурентности
Комплексное руководство по языку программирования Erlang и платформе OTP. Объясняются основы синтаксиса, функционального подхода и работы со списками. Детально разбирается модель акторов для конкурентности с примерами кода процессов, а также введение в OTP-библиотеки (GenServer, Supervisor) для создания отказоустойчивых систем.
331
1
Комментарии (8)