Организация проекта и экосистема dune
Собираем знания о модулях в практику: как структурировать настоящий проект с библиотеками, интерфейсами и зависимостями.
Файл .ml — реализация модуля, парный файл .mli — его публичный интерфейс; dune связывает их в библиотеки и исполняемые файлы.
Теперь, когда модули понятны, посмотрим, как из них складывается реальный проект. На практике вы редко пишете module ... = struct вручную — вместо этого раскладываете код по файлам, а интерфейсы выносите в .mli.
Файлы .ml и .mli
Файл geometry.ml становится модулем Geometry. Если рядом положить geometry.mli, он сыграет роль сигнатуры.
(* geometry.mli — публичный интерфейс *)
type shape
val circle : float -> shape
val area : shape -> float
(* geometry.ml — реализация *)
type shape = Circle of float | Rect of float * float
let circle r = Circle r
let area = function
| Circle r -> 3.14159 *. r *. r
| Rect (w, h) -> w *. h
(* конструкторы скрыты: тип shape абстрактен *)
Пользователь создаёт фигуры только через circle, а внутреннее устройство shape можно менять, не ломая чужой код.
Структура проекта
myproject/
dune-project
lib/
dune
geometry.ml
geometry.mli
bin/
dune
main.ml
test/
dune
test_geometry.ml
Принято разделять: lib/ — переиспользуемая библиотека, bin/ — точка входа, test/ — тесты.
Файлы dune
# lib/dune
(library
(name geometry))
# bin/dune
(executable
(name main)
(libraries geometry str))
В main.ml модуль из библиотеки доступен по имени: Geometry.area (Geometry.circle 2.0).
Зависимости через opam
| Пакет | Назначение |
Base / Core | альтернативная стандартная библиотека (Jane Street) |
Lwt / Async | асинхронность и конкурентность |
Dream | веб-фреймворк |
Alcotest / QCheck | тестирование |
Как работает под капотом
dune анализирует обращения к модулям, строит граф зависимостей между файлами и компилирует их в правильном порядке, кешируя результаты. Когда есть .mli, dune сперва компилирует интерфейс, затем проверяет реализацию против него — поэтому несоответствие сигнатуре всплывёт при сборке. Команда dune build @runtest прогоняет тесты, dune fmt форматирует код, dune utop lib запускает REPL с загруженной библиотекой. Вся инфраструктура (opam + dune + LSP) делает разработку на OCaml комфортной.
Частые ошибки
- Забыть добавить библиотеку в
(libraries ...). Тогда модуль «не найден» при сборке. - Несоответствие
.mlи.mli. Если реализация не предоставляет что-то из интерфейса — ошибка компиляции. - Класть всё в один гигантский файл. Разбивайте на модули по ответственности.
Итоги
.ml— реализация модуля, парный.mli— публичный интерфейс с возможной абстракцией типов.- dune описывает библиотеки, исполняемые файлы и тесты; зависимости — в
(libraries ...). - Экосистема (opam, Base/Core, Lwt, Dream, Alcotest) покрывает асинхронность, веб и тестирование.