BEAM давно ценят за лёгкие процессы и умение переживать падения отдельных компонентов. Но динамическая типизация в Erlang и Elixir оставляет много ошибок на потом: опечатка в атоме или неверная форма сообщения всплывает в рантайме. Gleam добавляет строгую статическую систему типов, сохраняя модель процессов и совместимость с OTP. Компилятор написан на Rust и выдаёт Erlang-байткод или JavaScript.

Что именно даёт статическая типизация

Gleam проверяет все ветви case на полноту, запрещает null и требует явно обрабатывать ошибки через Result. Компилятор знает форму данных на каждом шаге, поэтому цепочка преобразований не разваливается в момент запроса. При этом среда выполнения остаётся BEAM — те же процессы, супервизоры и наблюдаемость.

Язык не пытается быть Erlang с другим синтаксисом. В нём нет макросов и динамических атомов из строки, зато есть алгебраические типы, обобщения и удобный pipeline. Для задач, где важна предсказуемость, это выигрыш; для метапрограммирования — ограничение.

Экосистема небольшая, но растущая: в Hex есть HTTP-серверы, драйверы Postgres, JSON и тестовые библиотеки. Пакеты на Gleam можно вызывать из Erlang и наоборот, поэтому зрелые библиотеки не приходится переписывать. Компилятор выдаёт понятные ошибки с номерами строк, а форматирование встроено в утилиту и не требует отдельного линтера.

Установка и первый проект

Компилятор ставится одним скриптом, дальше проект создаётся встроенной утилитой. Она же генерирует структуру, тесты и файл зависимостей.

curl -fsSL https://gleam.run/install.sh | sh
gleam new gateway
cd gateway
gleam run

В gleam.toml указывают имя, версию и цель сборки. По умолчанию target = «erlang»; для фронтенда или edge-среды переключают на javascript. Зависимости добавляют командой gleam add, они публикуются в Hex.

gleam add gleam_otp
gleam add wisp
gleam build
gleam test

Типы и разбор вариантов

Вместо набора констант описывают собственный тип с параметрами. Компилятор заставит обработать каждый вариант, а забытую ветку покажет как ошибку, а не как исключение в проде.

import gleam/string

pub type Status {
  Active
  Suspended(reason: String)
  Deleted(at: Int)
}

pub fn describe(status: Status) -> String {
  case status {
    Active -> "active"
    Suspended(reason) -> string.concat(["suspended: ", reason])
    Deleted(_) -> "deleted"
  }
}

Списки и кортежи неизменяемы, а рекурсия заменяет циклы. Это непривычно после императивных языков, но хорошо ложится на обработку сообщений и обход деревьев.

Обобщения позволяют писать функции, не привязанные к конкретному типу: например, обёртку над Result или разбор списка. Компилятор выводит параметры сам, поэтому аннотации нужны не всегда. Если тип вывести нельзя, ошибка появится на сборке, а не в момент выполнения.

Ошибки как значения

Result — основной способ вернуть неуспех. Никаких исключений для ожидаемых ситуаций: вызывающий обязан решить, что делать. Оператор конвейера передаёт значение в следующий вызов и делает цепочки читаемыми.

import gleam/int

pub fn parse_port(raw: String) -> Result(Int, String) {
  case int.parse(raw) {
    Ok(port) if port > 1024 -> Ok(port)
    Ok(_) -> Error("port too low")
    Error(_) -> Error("not a number")
  }
}

Код не скомпилируется, пока не обработаны оба исхода. В связке с pattern matching это убирает целый класс падений, которые в динамическом языке ловят только тестами.

Процессы и OTP

Поверх BEAM доступны акторы из gleam_otp. Актор — это процесс с состоянием и функцией обработки сообщений; супервизор перезапускает его при сбое. Типизированные сообщения убирают ошибки, знакомые по «сырым» атомам.

import gleam/otp/actor

pub fn start() -> Result(actor.Started(Int), actor.StartError) {
  actor.start(0, handle)
}

fn handle(state: Int, delta: Int) -> actor.Next(Int, Int) {
  actor.continue(state + delta)
}

Точные сигнатуры стоит сверять с документацией пакета: API развивается. Смысл остаётся прежним: состояние явное, сообщение типизировано, а поведение при падении задаёт супервизор.

Дерево супервизоров строят так же, как в Erlang: дочерние процессы получают стратегию перезапуска и лимит попыток. Если актор падает из-за ошибки в данных, супервизор перезапустит его с чистым состоянием, а не уронит весь сервис. Для долгоживущих соединений это важнее, чем скорость обработки одного запроса.

Веб-сервис и интероп

Для HTTP обычно берут wisp и mist: первый даёт удобные запросы и ответы, второй — сервер. Обработчик остаётся обычной функцией, которую легко тестировать без сети.

import wisp

pub fn handle(request: wisp.Request) -> wisp.Response {
  case wisp.path_segments(request) {
    ["health"] -> wisp.ok()
    _ -> wisp.not_found()
  }
}

Старый код на Erlang подключают через external. Это же окно для библиотек, которых пока нет в Hex.

@external(erlang, "crypto", "strong_rand_bytes")
fn random_bytes(count: Int) -> BitArray

Тесты для обработчика не поднимают сеть: запрос собирают как структуру, ответ проверяют по статусу и телу. Это делает проверку дешёвой и быстрой, а интеграционные тесты остаются для проверки сборки целиком. Для фоновых задач используют те же акторы и супервизоры, что и в Erlang.

Сборка и деплой

Релиз собирается в самодостаточный каталог с Erlang-рантаймом и скриптом запуска. Дальше его кладут в образ или на сервер и запускают под systemd.

gleam export erlang-shipment
ls build/erlang-shipment
./build/erlang-shipment/entrypoint.sh run

Для JavaScript-цели достаточно gleam build —target javascript. Помните, что на JS нет процессов BEAM: модель конкурентности там другая, и код с акторами придётся переписать.

В образ кладут только каталог сборки и минимальный рантайм, поэтому итоговый слой получается небольшим. Конфигурацию передают переменными окружения, секреты — через файлы или менеджер секретов. Для systemd достаточно указать WorkingDirectory, ExecStart и User, а перезапуск доверить параметру Restart=on-failure. Проверьте, что версия рантайма на сервере совпадает с той, под которую собирали релиз.

Когда Gleam не подойдёт

  • Нужны макросы и глубокая метапрограмма — смотрите Elixir.
  • Требуется зрелая библиотека под редкий протокол — проверьте Hex заранее.
  • Команда не готова к строгим типам и явным ошибкам.
  • Проект живёт на другой виртуальной машине и BEAM не нужен.

Итог

Gleam — практичный способ получить статическую типизацию на BEAM без отказа от OTP и экосистемы Erlang. Начните с небольшого сервиса: пара модулей, тесты, релиз через shipment. Если типы и явные Result ускоряют работу команды, расширяйте; если борьба с компилятором съедает время, у вас останется работающий код на Erlang или Elixir.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте