Типы модулей OCaml и отдельная компиляция - PullRequest
8 голосов
/ 23 марта 2012

Я читаю Статья ведущего дизайнера OCaml 1994 года о модулях, типах и отдельной компиляции. (любезно указал мне Норман Рэмси в другой вопрос ). Я понимаю, что в статье обсуждается происхождение существующей системы типов / сигнатур модуля OCaml. При этом автор предлагает непрозрачную интерпретацию объявлений типов в сигнатурах (для обеспечения отдельной компиляции) вместе с декларациями типов манифестов (для выразительности). Пытаясь собрать несколько собственных примеров, чтобы продемонстрировать, какие проблемы пытается решить нотация сигнатуры модуля OCaml, я написал следующий код в двух файлах:

В файле ordering.ml (или .mli - я пробовал оба) ( файл A ):

module type ORDERING = sig
 type t
 val isLess : t -> t -> bool
end

и в файле useOrdering.ml ( файл B ):

open Ordering
module StringOrdering : ORDERING
  let main () =
    Printf.printf "%b" StringOrdering.isLess "a" "b"
  main ()

Идея состоит в том, чтобы ожидать, что компилятор пожалуется (при компиляции второго файла), что в модуле StringOrdering недостаточно информации о типе для проверки типа приложения StringOrdering.isLess (и, таким образом, мотивирует необходимость синтаксиса with type ). Однако, хотя файл A компилируется должным образом, файл B заставляет 3.11.2 ocamlc жаловаться на синтаксическую ошибку. Я понял, что подписи предназначены для того, чтобы позволить кому-то писать код на основе сигнатуры модуля без доступа к реализации (структуре модуля).

Признаюсь, я не уверен в синтаксисе: module A : B, с которым я столкнулся в , в этой довольно старой статье о раздельной компиляции , но меня удивляет, существует ли такой или подобный синтаксис (без привлечения функторов) позволить кому-то писать код, основанный только на типе модуля, с фактической структурой модуля, предоставляемой во время компоновки, подобно тому, как можно использовать файлы *.h и *.c в C / C ++. Без такой возможности казалось бы, что типы / подписи модулей в основном предназначены для запечатывания / скрытия внутренних элементов модулей или более явной проверки / аннотации типов, но не для отдельной / независимой компиляции.

На самом деле, глядя на раздел руководства OCaml по модулям и отдельной компиляции , кажется, что моя аналогия с модулями компиляции C не работает, потому что руководство OCaml определяет единицы компиляции OCaml как A.ml и A.mli duo, тогда как в C / C ++ файлы .h вставляются в модуль компиляции любого импортирующего файла .c.

Ответы [ 3 ]

6 голосов
/ 23 марта 2012

Правильный способ сделать это - сделать следующее:

  1. В заказе.mli пишите:

    (* This define the signature *)
    module type ORDERING = sig
      type t
      val isLess : t -> t -> bool
    end
    
    (* This define a module having ORDERING as signature *)
     module StringOrdering : ORDERING
    
  2. Скомпилируйте файл: ocamlc -c ordering.mli

  3. В другом файле см. Скомпилированную подпись:

    open Ordering
    
    let main () =
      Printf.printf "%b" (StringOrdering.isLess "a" "b")
    
    let () = main ()
    

    Когда вы компилируете файл, вы получаете ожидаемую ошибку типа (т. Е. string не совместим с Ordering.StringOrdering.t). Если вы хотите удалить ошибку типа, вы должны добавить ограничение with type t = string к определению StringOrdering в ordering.mli.

Итак, ответьте на второй вопрос: да, в режиме байт-кода компилятору просто нужно знать об интерфейсах, от которых вы зависите, и вы можете выбрать, какую реализацию использовать во время соединения. По умолчанию это не так для компиляции собственного кода (из-за межмодульной оптимизации), но вы можете отключить его.

4 голосов
/ 24 марта 2012

Вероятно, вас просто смущает связь между явными определениями модулей и сигнатур и неявным определением модулей в файлах .ml / .mli.

По сути, если у вас есть файл a.ml и вы используете его внутри какого-то другого файла, то это как если бы вы написали

module A =
struct
  (* content of file a.ml *)
end

Если у вас также есть a.mli, значит, вы написали

module A :
sig
  (* content of file a.mli *)
end =
struct
  (* content of file a.ml *)
end

Обратите внимание, что это определяет только модуль с именем A, а не модуль тип . Подпись A не может быть названа через этот механизм.

Другой файл, использующий A, может быть скомпилирован с использованием только a.mli, без предоставления файла a.ml. Однако вы хотите убедиться, что вся информация о типах сделана прозрачной, где это необходимо. Например, предположим, что вы должны определить карту над целыми числами:

(* intMap.mli *)
type key = int
type 'a map
val empty : 'a map
val add : key -> 'a -> 'a map -> 'a map
val lookup : key -> 'a map -> 'a option
...

Здесь key сделан прозрачным, потому что любой клиентский код (из модуля IntMap, который описывает эта подпись) должен знать, что значит быть в состоянии добавить что-то на карту. Однако сам тип map может (и должен) оставаться абстрактным, потому что клиент не должен связываться с деталями его реализации.

Отношение к заголовочным файлам C состоит в том, что те, в основном only , допускают прозрачные типы. В Окамле у вас есть выбор.

3 голосов
/ 26 марта 2012

module StringOrdering : ORDERING является декларацией модуля.Вы можете использовать это в подписи, чтобы сказать, что подпись содержит поле модуля с именем StringOrdering и подписью ORDERING.Это не имеет смысла в модуле.

Вам нужно определить модуль где-нибудь, который реализует необходимые вам операции.Определение модуля может выглядеть примерно так:

module StringOrderingImplementation = struct
  type t = string
  let isLess x y = x <= y
end

Если вы хотите скрыть определение типа t, вам нужно создать другой модуль, в котором определение будет абстрактным.Операция по созданию нового модуля из старого называется герметизацией и выражается через оператор :.

module StringOrderingAbstract = (StringOrdering : ORDERING)

Тогда StringOrderingImplementation.isLess "a" "b" хорошо напечатан, тогда как StringOrderingAbstract.isLess "a" "b" не может бытьтипизируется, поскольку StringOrderingAbstract.t является абстрактным типом, который не совместим с string или любым другим существующим типом.На самом деле невозможно создать значение типа StringOrderingAbstract.t, поскольку модуль не содержит конструктора.

Если у вас есть модуль компиляции foo.ml, это модуль Foo, аподпись этого модуля задается интерфейсным файлом foo.mli.То есть файлы foo.ml и foo.mli эквивалентны определению модуля

module Foo = (struct (*…contents of foo.ml…*) end :
              sig (*…contents of foo.mli…*) end)

При компиляции модуля, использующего Foo, компилятор смотрит только на foo.mli (или, скорее, на результатего компиляции: foo.cmi), а не foo.ml ¹.Вот как интерфейсы и отдельная компиляция сочетаются друг с другом.C нуждается #include <foo.h>, потому что ему не хватает пространства имен;в OCaml Foo.bar автоматически ссылается на bar, определенный в модуле компиляции foo, если в области нет другого модуля с именем Foo.

¹ На самом деле, компилятор собственного кодасмотрит на реализацию Foo для выполнения оптимизации (встраивание).Тип проверки никогда не смотрит ни на что, кроме того, что в интерфейсе.

...