Search code examples
compiler-errorsocamlparametric-polymorphismfirst-class-modules

Unpacking a first-class module constrained by a type variable


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?

Full example/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 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.


Solution

  • 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.