Распаковка первоклассного модуля, ограниченного переменной типа

Я пытаюсь написать функцию, которая в основном выглядит так:

module type M = sig
  type t
  val doStuff : t -> unit
end

let f : 'a. 'a -> (module M with type t = 'a) -> unit
      = fun value (module MSomething) -> MSomething.doStuff value

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

Тип этого упакованного модуля содержит переменные

Однако я обнаружил, что все еще могу заставить это работать, если я заверну его в GADT, который 1) делает 'a экзистенциальный и 2) предоставляет преобразователь из другой параметризованной переменной типа в экзистенциальную:

type 'b gadt =
  GADT: ('b -> 'a) * (module M with type t = 'a) -> 'b gadt

let f value (GADT (convert, (module MSomething))) =
  MSomething.doStuff (convert value)

Сам по себе GADT не доставляет неудобств1, но я бы очень хотел избежатьconvertфункция, поскольку она не служит никакой другой цели, кроме как помочь компилятору. Возможно ли это как-нибудь?

Полный пример /MCVE

module type M = sig
  type t
  val doStuff : t -> unit
end

module MInt = struct
  type t = int
  let doStuff = print_int
end

let f : 'a. 'a -> (module M with type t = 'a) -> unit
      = fun value (module MSomething) -> MSomething.doStuff value

let () = f 42 (module MInt : M with type t = int)
let () = print_newline ()

1 На самом деле мне нужен GADT, потому что мне нужно, чтобы модуль параметризовался другим экзистенциальным модулем, чтобы я мог объединить модули с разными типами в список. Но для простоты я пропустил это в первом примере выше.

1 ответ

Решение

С модулями первого класса (как и для любого локального модуля) вы должны обращаться к локально абстрактным типам, а не к явным полиморфным аннотациям:

let f (type a) (value:a) (module M: M with type t = a) = M.doStuff value

работает нормально.

Другие вопросы по тегам