Потеря точности типа из сигнатуры модуля - PullRequest
0 голосов
/ 17 сентября 2018

Допустим, у меня был простой модуль MyFoo, который выглядит примерно так

module MyFoo = struct
  type t =
    | A of string
    | B of int

  let to_string thing =
    match thing with
    | A str -> str
    | B n -> string_of_int n
end

С этим определением он отлично работает и, как и ожидалось, я могу сделать что-то вроде

let _ = MyFoo.A "";;
- : MyFoo.t = MyFoo.A ""

без проблем.

Теперь, может быть, я хочу создать функтор, который будет использовать модули с этой структурой, поэтому я определяю сигнатуру модуля, которая в общем описывает, как это выглядит, и называю ее BaseFoo

module type BaseFoo = sig
  type t
  val to_string : t -> string
end

Если я переопределю MyFoo таким же образом, но поставлю эту подпись как

module MyFoo : BaseFoo = struct
  type t =
    | A of string
    | B of int

  let to_string thing =
    match thing with
    | A str -> str
    | B n -> string_of_int n
end

Я теряю точность его типа t (есть ли лучший способ описать, что здесь происходит?) - например:

let _ = MyFoo.A "";;
Error: Unbound constructor MyFoo.A

Что именно здесь происходит и почему это происходит? Есть ли канонический способ решения этой проблемы (кроме простого отказа от подписи)?

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

module MyFoo : sig
  include BaseFoo
  type t = | A of string | B of int
end = struct
  type t =
    | A of string
    | B of int
  let to_string thing =
    match thing with
    | A str -> str
    | B n -> string_of_int n
end

let _ = MyFoo.A "test";;
Error: Multiple definition of the type name t.
       Names must be unique in a given structure or signature.

1 Ответ

0 голосов
/ 17 сентября 2018

Вам не нужна подпись

То, что происходит, в значительной степени описывает то, что вы описываете: предоставление MyFoo подписи BaseFoo в ее определении ограничивает ее подписью.

Почему? Потому что для этого нужно указать подпись в этом месте. Каноническое решение - оставить подпись (обычно, чтобы определение подписи рядом с определением модуля было достаточно ясным для читателя).

Обратите внимание, что при вызове MyFoo на вашем функторе будет проверена подпись. Мой обычный выбор - полагаться на это.

Несколько обходных путей

Учитывая то, что вы пробовали, я думаю, это может быть вам интересно:

module type BaseFoo = sig  ... end
module MyFoo = struct ... end

module MyFooHidden : BaseFoo = MyFoo (* Same as defining MyFoo : BaseFoo *)
module MyFooWithType :
   BaseFoo with type t = MyFoo.t
   = MyFoo (* What you want *)

Предложение with type t = t' позволяет аннотировать сигнатуру модуля для добавления информации о типе. Это очень полезно, особенно при работе с функторами. См. здесь для получения дополнительной информации.

MyFooHidden может показаться бесполезным, но вы можете видеть это как проверку того, что MyFoo имеет правильную подпись. Вы все еще можете использовать MyFoo так, как хотите. MyFooWithType на самом деле (немного) менее полезно, потому что если вы измените свою подпись, добавив тип, который вы хотите экспортировать, вам нужно будет добавить экспорт и здесь.

Использование include

Что касается вашей include попытки. Ну, хорошая попытка! Вы были почти там:

module MyFoo : sig
 type t = A of string | B of int
 include BaseFoo with type t := t
end

with type t := t' немного отличается тем, что выполняет не равенство, а замену. Определение типа t полностью удалено из подписи BaseFoo, а все экземпляры заменены на ваши собственные t, таким образом у вас не возникнет проблем с двойным определением. См. здесь для получения более подробной информации.

Как вы указали, это, вероятно, не тот подход, который вам нужен, поскольку вы больше не можете легко понять, что MyFoo действительно BaseFoo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...