Как включить модуль внутри другого модуля, сохранив их подписи?

Я пытаюсь реализовать небольшой пример повторного использования модулей внутри других модулей, но пока мне это не удалось.

Чего я хотел бы добиться, так это создать 2 подписи - S и SE (с SE, включая S) и 2 модуля - M (реализующий S) и ME (реализующий SE) с целью не повторять предыдущий код.

Бит, который мне не хватает, включает содержимое M внутри ME:

module type S = sig
  type t
  val of_int : int -> t
end

module type SE = sig
  include S
  val to_string : t -> string
end

module M : S = struct
  type t = int
  let of_int x = x
end

module ME : SE = struct
  (* include M *)
  let to_string = string_of_int
end

Пока нашел обходной путь, раскомментировав (* include M *) в ME и изменив определение M на module M : S with type t = int = struct, но это противоречит цели этого упражнения, поскольку оно меняет определение M с реализации S на реализацию чего-то похожего на S.

Конечно, должно быть правильное решение для этого упражнения. Итак, что мне не хватает?


1
51
1

Ответ:

Решено

Проблема в том, что после того, как вы ограничили подпись M до S, информации о M.t недостаточно для реализации осмысленной функции to_string.

Действительно, модуль типа S определяет черный ящик (абстрактный тип) t, который может быть получен из целого числа... и все. Другими словами, вы можете создавать только значения типа M.t, но никогда не имеете ни малейшего представления о содержании этих значений. Таким образом, единственный вариант, оставшийся для to_string, — игнорировать его аргумент и возвращать несвязанную строку. Например,

 module ME: SE = struct
   include M
   let to_string _ = "?"
 end

Определение M и ME в обратном порядке работает лучше. Во-первых, мы определяем модуль с более исчерпывающим API:

module ME : SE = struct
  type t = unit
  let of_int _ = ()
  let to_string () = "()"
end

тогда мы можем стереть функцию to_string с ограничением подписи

module M: S = ME

Другой вариант — не делать тип t абстрактным, как вы обнаружили, используя либо точный тип модуля S with type t = int, либо позволяя компилятору сделать его вывод.

Короче говоря, ограничения подписи касаются стирания информации: они позволяют скрыть некоторую информацию о реализации. А сокрытие слишком большого количества информации может привести к бесполезным модулям.