Распаковка первоклассного модуля, ограниченного переменной типа
Я пытаюсь написать функцию, которая в основном выглядит так:
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
работает нормально.