I'm trying to write a function that basically looks like this:
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
That is, a function that takes any type of value, and an associated module containing one or more functions that can operate on this value. Unfortunately the above will have the compiler complaining that
The type of this packed module contains variables
However, I've found that I can still get this to work if I wrap it in a GADT that 1) makes 'a
an existential and 2) provides a converter from another parameterized type variable to the existential:
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)
The GADT itself isn't a nuisance1, but I'd very much like to avoid the convert
function since it doesn't serve any purpose other than to help the compiler out. Is this possible somehow?
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 I actually want the GADT because I need the module to be parameterized by a different existential so I can put differently typed modules together in a list. But for simplicity's sake I've omitted that from the first example above.
With first class modules (like for any local module) you should reach for locally abstract types and not explicit polymorphic annotations:
let f (type a) (value:a) (module M: M with type t = a) = M.doStuff value
works just fine.