Я пытаюсь понять определенную вещь о ocaml модулях и их компиляции:
я вынужден повторно объявить типы, уже объявленные в a .mli
в определенном .ml
реализации?
Только дать пример:
(* foo.mli *)
type foobar = Bool of bool | Float of float | Int of int
(* foo.ml *)
type baz = foobar option
Это, согласно моему нормальному образу мыслей об интерфейсах/реализациях, должно быть в порядке, но он говорит
Ошибка: Несвязанный конструктор типа foobar
при попытке скомпилировать с
ocamlc -c foo.mli
ocamlc -c foo.ml
Конечно, ошибка исчезает, если я объявляю foobar
внутри foo.ml
также, но это кажется сложным путем, так как я должен сохранить вещи синхронизировавшими на каждом изменении.
Существует ли способ избежать этого дублирования, или я вынужден повторно объявить типы каждый раз?
Заранее спасибо
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
, либо с дублированием.
Вы можете позволить ocamlc сгенерировать для вас файл mli из файла ml:
ocamlc -i some.ml > some.mli
В общем, да, от вас требуется дублировать типы.
Вы можете обойти это, однако, с помощью Camlp4 и расширения синтаксиса pa_macro
(пакет findlib: camlp4.macro
). Он определяет, среди прочего, и конструкцию INCLUDE. Вы можете использовать его для выделения общих определений типов в отдельный файл и включения этого файла в файлы .ml
и .mli
. Однако я не видел, чтобы это было сделано в развернутом проекте OCaml, поэтому я не знаю, можно ли это квалифицировать как рекомендуемую практику, но это возможно.
Однако грамотное программирование чище IMO.
Да, вам придется заново объявлять типы. Единственные известные мне способы обойти это -
Не использовать .mli файл; просто раскрывать все без интерфейса. Ужасная идея.
Использовать инструмент грамотного программирования или другой препроцессор, чтобы избежать дублирования объявлений интерфейсов в единственном истинном источнике. Для больших проектов мы делаем это в моей группе.
Для маленьких проектов мы просто дублируем объявления типов. И ворчим по этому поводу.