How to unpack a dynamic module?

module type Log = {
  type t
  let toString: t => string
}

let log = (content: 'a, logMod: module(Log with type t = 'a)) => {
  module LogMod = unpack(logMod) // here is not allowed, how to fix it?
  content->LogModule.toString->Js.log
}

log(
  "a",
  module(
    {
      type t = string
      let toString = v => v
    }
  ),
)

log(
  1,
  module(
    {
      type t = int
      let toString = v => v->Belt.Int.toString
    }
  ),
)


playground

You need to introduce a locally abstract type, eg type a. Basically it introduces a type constructor named a that is abstract within its scope, and it gets replaced by a fresh type variable. You can then use it in places where type variables are not allowed. One example where this is not allowed is the one you found…in the first-class module with the abstract type.

You can use it like this (playground) :

// type a is locally abstract type
let log = (type a, content: a, log: module(Log with type t = a)) => {
  module Log = unpack(log)
  content->Log.toString->Js.log
}

And, btw, you can “unpack” the first-class module right in the function parameter to make it a bit more terse:

// Unpack in right in the function param...
let log' = (type a, content: a, module(Log: Log with type t = a)) => {
  content->Log.toString->Js.log
}

More reading…

2 Likes

@Ryan Thank you so much! Really really hoping our team will updating the Rescript documentation.