Типы данных: атомы, числа, кортежи, списки, map, бинарные данные
Палитра значений, из которых строятся программы на Erlang.
Терм (term) — любое значение в Erlang: число, атом, кортеж, список и так далее. Всё, чем оперирует программа, — это термы.
У Erlang небольшой, но выразительный набор типов. Разобравшись с ними, вы поймёте большую часть кода. Это сознательная черта языка: типов нарочно немного, и каждый занимает чёткую нишу. Освоив этот короткий список, вы получаете почти полный словарь, на котором написаны любые программы на Erlang, от учебных до промышленных. Пройдёмся по основным.
Атомы
Атом — это именованная константа, значением которой является само имя. Атомы пишутся с маленькой буквы (или в одинарных кавычках). Их используют как метки и флаги: ok, error, true, false, undefined. Если вы программировали на других языках, ближайшая аналогия — элементы перечисления (enum) или символы (symbols) из Ruby и Lisp. Атом не несёт никакой «начинки», кроме собственного имени, и в этом вся его суть: ok значит ровно «ok», ни больше ни меньше. Атомы пронизывают весь Erlang — имена модулей и функций, флаги, теги результатов — всё это атомы, поэтому привыкнуть к ним стоит сразу.
Status = ok,
Color = 'Красный', % атом с заглавной — в кавычках
Result = error.
Числа
Целые числа в Erlang произвольной точности — они не переполняются, а растут по мере надобности. Это приятное свойство для тех, кто хоть раз ловил коварную ошибку переполнения в C или Java: здесь факториал большого числа или вычисление с гигантскими значениями просто работают, без молчаливого «обнуления» старших разрядов. Есть и числа с плавающей точкой — для них действуют обычные оговорки о неточности дробной арифметики, поэтому деньги, например, лучше хранить в целых (в копейках), а не во float.
A = 42,
Big = 2 bsl 1000, % огромное число, без переполнения
Pi = 3.14159,
Hex = 16#FF. % 255 в шестнадцатеричной
Кортежи (tuples)
Кортеж — это фиксированный набор значений в фигурных скобках: {...}. Кортежи удобны, когда количество элементов известно и каждый имеет смысл по позиции. Очень распространён шаблон «тег + данные»: первым элементом кладут атом-метку, а дальше — связанные с ней данные. Именно так в Erlang принято возвращать результат, который может быть успехом или ошибкой: {ok, Значение} либо {error, Причина}. Вызывающий код смотрит на первый элемент и понимает, как трактовать остальное. Этот скромный приём — один из самых узнаваемых идиом языка, вы встретите его буквально в каждом проекте.
Point = {3, 4},
Person = {person, "Анна", 30},
Reply = {ok, "данные получены"}.
Списки
Список — последовательность элементов в квадратных скобках. У списка есть «голова» (первый элемент) и «хвост» (всё остальное). Оператор | разделяет голову и хвост.
Nums = [1, 2, 3, 4],
Head = hd(Nums), % 1
Tail = tl(Nums), % [2,3,4]
WithZero = [0 | Nums]. % [0,1,2,3,4]
Строки в Erlang — это, по сути, списки кодов символов. "abc" равно [97, 98, 99]. Это историческое решение иногда удивляет: строка и список чисел — одно и то же, и оболочка может неожиданно показать ваш список чисел как строку, если все они окажутся печатными кодами символов. Для серьёзной работы с текстом сегодня чаще используют бинарные строки (о них ниже) — они компактнее и эффективнее. Но понимать, что классическая строка есть список, важно: это объясняет многие особенности поведения и сообщений об ошибках.
Map — ассоциативный массив
Map (отображение) хранит пары «ключ — значение», как словарь. Это современная и удобная структура для конфигураций и записей; по сравнению со старыми способами хранить именованные поля она читается куда естественнее. Ключом может быть значение любого типа, а доступ к значению по ключу — быстрый. Важно помнить про неизменяемость: «обновляя» map синтаксисом Map#{ключ => новое}, вы получаете новый map, а исходный остаётся прежним. Map отлично подходит, когда набор полей заранее не фиксирован или может расти, — например, для конфигурации или для представления записи о пользователе.
User = #{name => "Иван", age => 25},
Name = maps:get(name, User), % "Иван"
Older = User#{age => 26}. % новый map с age=26
Бинарные данные
Бинарные данные — это эффективная последовательность байтов, записываемая в двойных угловых скобках. Их применяют для сетевых протоколов, файлов, кодирования, а также для текста в UTF-8. Это одна из сильных сторон Erlang и прямое наследие его телеком-прошлого: разбирать пакеты протоколов на уровне отдельных битов — повседневная задача в телекоме, и язык даёт для этого исключительно удобный синтаксис сопоставления по образцу. В примере ниже мы одним выражением «отрезаем» от бинарного значения первые два байта и забираем остаток как новую бинарную последовательность — попробуйте представить, сколько кода заняла бы та же операция на низкоуровневом языке с ручной работой с указателями.
Bin = <<1, 2, 3>>,
Text = <<"привет">>,
<<A, B, Rest/binary>> = <<10, 20, 30, 40>>.
% A=10, B=20, Rest = <<30,40>>
Как работает под капотом
Атомы хранятся в общей таблице атомов: одинаковый атом — это всегда одна и та же запись, поэтому сравнение атомов мгновенно. По сути, внутри атом представлен небольшим числом-индексом, и проверка «равны ли два атома» сводится к сравнению этих чисел — отсюда и скорость. Но у этого приёма есть оборотная сторона. Важно: таблица атомов не очищается сборщиком мусора, поэтому нельзя бесконтрольно превращать строки в атомы (можно переполнить таблицу). По умолчанию в ней около миллиона мест, и если их исчерпать, виртуальная машина аварийно остановится целиком — это один из немногих способов «положить» весь узел Erlang.
У остальных типов своя механика, и понимание её помогает писать быстрый код. Кортежи лежат в памяти подряд — доступ по индексу быстрый, зато «изменение» кортежа требует создать новый. Списки — связные, поэтому добавление в голову (через |) дёшево и происходит за постоянное время, а вот добавление в хвост или обращение к последнему элементу дороги, ведь приходится пройти весь список. Отсюда практический вывод, который вы будете применять постоянно: накапливайте результат, добавляя в голову, а в самом конце один раз переверните список функцией lists:reverse. Это типичный и идиоматичный для Erlang приём, и он гораздо быстрее наивной дописки в хвост в цикле.
Частые ошибки
- Создавать атомы из пользовательского ввода. Таблица атомов конечна — это путь к падению. Используйте бинарные строки.
- Путать кортеж и список. Кортеж — фиксированная структура по позиции, список — переменная последовательность.
- Добавлять элементы в хвост списка в цикле. Это медленно; накапливайте в голову и переворачивайте.
Итоги
- Атомы — именованные константы (
ok,error); сравниваются мгновенно. - Числа — произвольной точности; кортежи — фиксированные наборы по позиции.
- Списки — последовательности с головой и хвостом; строки — это списки кодов.
- Map — словарь «ключ-значение»; бинарные данные — эффективные байтовые последовательности.