While learning OCaml, I have been surprised by inconsistency between automatic wrapping of “compilation units” and system of modules subtyping: Incomplete units
Is it still a thing and have to resolve in creation of nested modules? I.e. is there a way to tell compiler to not generate wrapping module for .ml file? Or a way to alter generated module’s signature?
I guess that by “wrapping module” you mean an interface file (.mli)? In the page you link, the author gives an example of two implementations of a stack data structure that are put in files listStack.ml and customStack.ml and argues that it is not possible to specify a single interface in a file stack.mli shared by both modules.
However, there is a fairly common idiom that achieves precisely this: you need to put the contents of the common signature in a file called, say, stack_intf.ml (note the .ml extension), and then create files listStack.mli and customStack.mli “including” Stack_intf. So you get in the end something that looks like this:
(* stack_intf.ml *)
module type S = sig
type 'a t
val empty : 'a t
...
end
(* listStack.ml *)
type 'a t = 'a list
let empty = []
...
(* listStack.mli *)
include Stack_intf.S
(* customStack.ml *)
type 'a t = ...
...
(* customStack.mli *)
include Stack_intf.S
This style is famously used by the “Base” (and “Core”) alternative standard libraries of OCaml.
Is that described way with “proxy” module type Stack_intf.S equivalent to subtyped module defined at toplevel? I mean equivalent to explicit declaration of module type and module definition:
module type Stack = sig
type 'a t
val empty : 'a t
end
module ListStack : Stack = struct
type 'a t = 'a list
let empty = []
end
It obviously introduces two new modules Stack_intf and Stack_intf.S. Are there other “side effects” with this trick?
The _intf trick solves the problem within the language, but sometimes it makes more sense to solve it with your build system, which can simply copy/symlink the .ml file to the name you need. I believe Dune supports this using select.