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