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.