First class modules polymorphism

Hello,

I have been doing some experimenting and am wondering if it is possible to write function signatures such that a first-class module type is treated ‘structurally’ so to speak.

Here is some code to help explain my question:

module type A = {
  let thing: string
}

module type B = {
  include A
  let anotherThing: int
}

let printThing = (module(SomeModule: A)) => {
  Console.log(SomeModule.thing)
}

let b: module(B) = module({
  let thing = "some thing"
  let anotherThing = 2
})

// Error:
// This has type: module(B)
// Somewhere wanted: module(A)
printThing(b)

module B_Module = unpack(b)
let bCast: module(A) = module(B_Module)

// Works :)
printThing(bCast)

Is there a signature for printThing which would allow it to accept any module of module type that ‘includes’ A? Alternatively, is there any shorter way to derive the equivalent of bCast from b or is explicitly unpacking and then re-packing it the only option?

1 Like

tl;dr: printThing(module(unpack(b)))

The modules are structurally typed (pretty much at least), and the signature you write for printThing does take any modules that satisfy the signature of A, but you do have to be a little explicit about it as you have seen.

Here are some options for different ways to do it: (playground)

module type A = {
  let thing: string
}

module type B = {
  include A
  let anotherThing: int
}

let printThing = (module(SomeModule: A)) => Js.log(SomeModule.thing)

let b: module(B) = module(
  {
    let thing = "some thing"
    let anotherThing = 2
  }
)

// Very short! No annotations!
printThing(module(unpack(b)))

// Don't need to annotate here.
module M = unpack(b)
printThing(module(M))

// Local module with let
{
  let module(M) = b
  module(M)->printThing
}

// Another option for local module
printThing({
  let module(M) = b
  module(M)
})

printThing({
  let module(M) = b
  // Just to show an annotation
  module(M: A)
})

// Just for reference, this doesn't need annotations either.
module B = {
  let thing = "some thing"
  let anotherThing = 2
}
printThing(module(B))

// Here's a fun thing...
module B': A = {
  let thing = "some thing"
  @warning("-32") let anotherThing = 2
}
printThing(module(B'))

Btw, I see you annotating types like let blah: module(Blah) = .... Sometimes when using stuff like this I will give the module type a nice little type like this: type blah = module(Blah), then use that as it looks a bit more like the value language rather than the module language. E.g.,

module type B = {
  include A
  let anotherThing: int
}

type b = module(B)

let b: b = module({
  let thing = "some thing"
  let anotherThing = 2
})
4 Likes

Very interesting, thank you!

1 Like