OCaml пытается заставить вас отделить интерфейс (.mli
) от реализации (.ml
. В большинстве случаев это хорошо; для значений вы публикуете тип в интерфейсе и сохраняете код в реализации. Можно сказать, что OCaml применяет определенное количество абстракций (интерфейсы должны быть опубликованы; код в интерфейсах отсутствует).
Для типов очень часто реализация совпадает с интерфейсом: оба утверждают, что тип имеет конкретное представление (и, возможно, объявление типа является генеративным). Здесь не может быть абстракции, потому что у разработчика нет никакой информации о типе, который он не хочет публиковать. (Исключение в основном при объявлении абстрактного типа.)
Один из способов взглянуть на это состоит в том, что интерфейс уже содержит достаточно информации для написания реализации. Учитывая интерфейс type foobar = Bool of bool | Float of float | Int of int
, возможна только одна реализация. Так что не пишите реализацию!
Распространенная идиома - иметь модуль, который предназначен для объявлений типов, и сделать так, чтобы он имел только .mli
. Поскольку типы не зависят от значений, этот модуль обычно входит очень рано в цепочку зависимостей. Большинство инструментов компиляции хорошо справляются с этим; например ocamldep
будет делать правильные вещи. (Это одно преимущество по сравнению с .ml
.)
Ограничением этого подхода является то, что вам также нужно несколько определений модуля здесь и там. (Типичным примером является определение типа foo
, затем модуля OrderedFoo : Map.OrderedType
с type t = foo
, а затем еще одно объявление типа, включающее 'a Map.Make(OrderedFoo).t
.) Они не могут быть помещены в файлы интерфейса. Иногда допустимо разбить ваши определения на несколько частей: сначала набор типов (types1.mli
), затем модуль (mod1.mli
и mod1.ml
), а затем несколько типов (types2.mli
). В других случаях (например, если определения являются рекурсивными) вы должны жить с .ml
без .mli
или дублированием.