OCaml — представитель семейства ML: строгая статическая типизация, вывод типов, неизменяемые значения по умолчанию и сопоставление с образцом. Язык компилируется в нативный код, имеет развитую модульную систему и применяется там, где цена ошибки высока: компиляторы, финансовые расчёты, статический анализ, распределённые системы. Ниже — установка через opam, рабочий проект на dune, типы, обработка ошибок и проверка качества. Примеры не требуют предыдущего опыта с функциональными языками, но предполагают знакомство с Linux и терминалом.
Что даёт строгая типизация
Компилятор OCaml выводит типы без аннотаций почти везде, но не делает поблажек: программа не соберётся, если варианты в match не покрывают все случаи. Нет null — вместо него option. Нет неявных приведений между числами и строками. Иммутабельность по умолчанию означает, что изменение состояния нужно объявить явно через mutable или ref, и это видно в коде. Модули и функторы позволяют собирать абстракции без наследования. Всё вместе даёт предсказуемость при рефакторинге: после изменения типа компилятор перечислит все места, которые нужно поправить.
Установка через opam
opam — менеджер пакетов и переключателей компилятора. Он позволяет держать несколько версий OCaml одновременно и не смешивать зависимости разных проектов.
sudo apt install opam m4 build-essential
opam init --disable-sandboxing
eval $(opam env)
opam switch create default 5.3.0
Флаг —disable-sandboxing нужен в контейнерах и на некоторых файловых системах, где bubblewrap недоступен. На обычном рабочем столе можно выполнить opam init без него. Версию 5.3.0 замените на актуальную из opam switch list-available. Дальше ставятся инструменты разработки.
opam install dune utop ocamlformat merlin ocaml-lsp-server
utop — интерактивная консоль, dune — система сборки, ocamlformat — форматтер, LSP-сервер даёт автодополнение в редакторе.
Проект на dune
Каркас создаётся одной командой. Внутри появляются каталоги bin, lib и test, файл dune-project и минимальный main.ml.
dune init proj ledger
cd ledger
dune build
dune exec main
Файл dune описывает исполняемый модуль и его библиотеки. Зависимости перечисляются явно, поэтому сборка воспроизводима.
(executable
(name main)
(libraries ledger))
Такой файл лежит в bin/dune. Библиотека ledger собирается из lib/dune с секцией library. Разделение на библиотеку и исполняемый файл упрощает тесты и повторное использование кода.
Типы и сопоставление с образцом
Варианты — основной способ моделировать состояние. Компилятор проверяет полноту match, а предупреждение 8 в dev-профиле dune превращается в ошибку.
type status =
| Pending
| Paid of int
| Refunded of int * string
let describe = function
| Pending -> "ожидает"
| Paid cents -> Printf.sprintf "оплачен на %d копеек" cents
| Refunded (cents, reason) ->
Printf.sprintf "возврат %d: %s" cents reason
Если добавить новый конструктор, сборка упадёт на неполном match — это и есть страховка от забытых веток. Вложенные структуры разбираются так же, без цепочек проверок и приведений.
Состояние и модули
Изменяемое состояние допустимо, но локально. Поле mutable в записи и модуль Map покрывают большинство задач.
type account = { id : int; mutable balance : int }
let deposit account amount =
if amount <= 0 then invalid_arg "amount";
account.balance <- account.balance + amount
module Accounts = Map.Make (Int)
Map.Make создаёт модуль сбалансированного дерева для ключей заданного типа. Операции возвращают новое дерево, а не меняют старое. Для больших коллекций есть Hashtbl, но он изменяемый и требует аккуратности.
Ошибки как значения
Исключения в OCaml есть, но для ожидаемых сбоев лучше result. Тогда вызывающий код обязан обработать ошибку, и это видно в типе.
let parse_amount s =
match int_of_string_opt s with
| Some n when n > 0 -> Ok n
| Some _ -> Error "сумма должна быть положительной"
| None -> Error "не число"
let ( let* ) = Result.bind
Оператор let* позволяет писать цепочки без вложенных match. Он входит в стандартную библиотеку Result начиная с OCaml 4.08, отдельная зависимость не нужна. Исключения оставьте для программных ошибок и нарушенных инвариантов.
Тесты и форматирование
Тесты живут рядом с кодом и запускаются через dune runtest. Alcotest даёт понятные сообщения и не требует отдельного раннера.
opam install alcotest
dune runtest
dune build @fmt --auto-promote
Секция test в test/dune подключает библиотеку и alcotest. Проверять стоит граничные случаи: ноль, отрицательные суммы, пустые строки, отсутствующие ключи. Форматтер запускается в CI той же командой, но без auto-promote. Перед первым запуском добавьте в корень файл .ocamlformat с версией установленного форматтера, иначе dune попросит конфигурацию.
Сборка и производительность
Dev-профиль включает отладочную информацию и проверки. Для замера нужен release.
dune build --profile release
perf stat ./_build/default/bin/main.exe
Нативный код OCaml сопоставим с C по скорости на типичных задачах, но многое зависит от аллокаций. Профилировщик perf показывает, где горячо; для статистики сборщика мусора полезен OCAMLRUNPARAM, а для точного анализа — библиотека memtrace. Оптимизацию начинайте после того, как тесты зафиксировали поведение.
Где язык уместен
OCaml силён в компиляторах и интерпретаторах, финансовых системах, статическом анализе, обработке протоколов и предметных языках. Порог входа выше, чем у Python, а вакансий меньше. Зато код, который компилируется, обычно работает предсказуемо, а рефакторинг не превращается в ручную проверку каждого вызова. Для небольших скриптов язык избыточен, для долгоживущего ядра с жёсткими требованиями к корректности — оправдан. Отдельно стоит упомянуть MirageOS: на OCaml собирают уникернелы, где приложение само выполняет роль операционной системы.
Чек-лист
- Отдельный opam switch на проект, версия компилятора зафиксирована.
- dune и ocamlformat в CI, сборка без предупреждений.
- Полные match и отказ от partial-функций вроде List.hd без проверки.
- Ожидаемые ошибки через result, исключения только для багов.
- Тесты на граничные значения и свойства, а не только на счастливый путь.
- Release-сборка и профилирование до оптимизаций.
Итог
OCaml даёт строгую типизацию без многословности и заставляет явно описывать состояния и ошибки. Связка opam и dune настраивается за десять минут, а компилятор берёт на себя рутинную проверку веток. Язык не станет массовым, но для компиляторов, финансовой логики и инфраструктурных утилит он остаётся сильным выбором. Начните с небольшой библиотеки и тестов: если модель данных ложится на варианты и модули, дальнейшая работа пойдёт легко.