Aliasing functor's submodules

Is there a way to alias a submodule of a functor? (Not sure if I’m using the terminology correctly here)

This is allowed:

module StringHeap = Binary (String)
module Submodule = StringHeap.Submodule

These are not:

let module StringHeap = Binary (String) in (* Syntax error, let-extension with punning expected *)
module Submodule = StringHeap.Submodule

Or:

module Submodule = (Binary (String)).Submodule (* Syntax error *)

How do I achieve this if I only want to expose Submodule and not the module it comes from?
One possible solution would be to factor out Submodule into its own functor, but I am interested in this particular way of achieving this without factoring it out.

Minimal example:
funkt.ml:

module type OrderedType = sig
  type t

  val compare : t -> t -> int
  val empty : t
end

module type S = sig
  type t

  module Submodule : sig
    val func : unit -> bool
  end

  val empty : t
  val is_empty : t -> bool
end

module Binary (Elt : OrderedType) : S = struct
  type t = Elt.t

  module Submodule = struct
    let func = failwith "Not yet implemented"
  end

  let empty = failwith "Not yet implemented"
  let is_empty h = Elt.compare Elt.empty h = 0
end

module StringHeap = Binary (String)
module Submodule = StringHeap.Submodule

let () =
  print_endline (string_of_bool StringHeap.(is_empty empty));
  print_endline (string_of_bool (Submodule.func ()))

dune:

(executable
  (name funkt)
  (public_name funkt)
  (libraries str))

dune-project:

(lang dune 3.7)
(package (name funkt))

Module aliases (i.e. re-exporting an existing module under a different name) cannot contain functor applications.
The usual way to solve your issue is to define both modules in the implementation, but only export one of them in the interface (.mli file).
If you don’t want to write an .mli file, you can use the following hack:

module Submodule = struct
  open! Binary(String)
  include Submodule
end

I agree that the error message isn’t great, and it would also be natural to allow module M = F(X).N directly (I don’t know if there are technical difficulties in implementing this feature).

1 Like

You can use the generalized open to bring definitions from a module into scope without exporting them, like this:

module Submodule = struct
  open Binary (String)
  include Submodule
end

(although this will copy all the fields of Submodule, which is a bit wasteful), or like this:

include struct
  open Binary(String)
  module Submodule = Submodule
end

(which won’t copy the fields)

1 Like