Hello! I am trying to build a modularized system in OCaml, which consists of two compilation units named Instr and Monitor along with run_file.ml that makes use of these two modules.
Monitor has a function called run_mon that expects as input another function of type unit -> t and the unit value, then it returns a value of type c. Meanwhile, Instr has a function run_inst that expects a unit value and returns a function of type unit -> r. This type information is found in the interface files for the two modules mentioned above. The types t, c and r are abstract types inside the interface files for the modules. Inside the implementation files, type t = c = r = unit.
Inside run_file.ml, I open these two modules and simply use them as follows:
let run () = Monitor.run_mon (Instr.run_inst ()) ()
However, when trying to compile run_file.ml, the compiler raises an error, highlighting Instr.run_inst ():
Error: this expression has type unit → Instr.r but an expression was expected of type unit → Monitor.t
Type Instr.r is not compatible with type Monitor.t
I cannot understand why the compiler is complaining about this issue, however, I am quite new to OCaml so there is something I’m missing here. Any ideas on how to interpret the error and how this issue can be solved would be great.
Yes, there are mli files for those modules, but the types in those mli files are abstract. So, for instance, the mli file for Instr contains the following:
type r
val run_inst: unit -> (unit -> r)
Then, in the ml file of this module is where I specify that r = unit. Similarly for the Monitor module.
Is this information hidden from the compiler? Have I done something wrong during compilation or should the implementation (ml) files make it clearer to the compiler in some way that r = c?
So how should the compilation process look like? I usually compile x.ml with x.mli, and similarly y.ml with y.mli, then in a file which uses them both I use the command:
The cmo files are not used at compilation time, only during linking or archiving. Thus ocamlc -c x.cmo y.cmo file.ml is functionally the same as ocamlc -c file.ml
The step-by-step process for compiling a compilation unit u.mli and u.ml :
compile the cmi files of the dependencies
compile u.mli into u.cmi
compile u.ml into u.cm{o,x}
Then once all cm{o,x} files have been built, they can be either archived into a cm{xa,a} library archive or linked into the final executable.
In other words, the mli file and the cmi describe the specification of your module to the external world, and when you write
type r
val run_inst: unit -> unit -> r
your specification tells the external world (aka anyone outside of instr.ml) that there is some type r which is produced by run_inst () (). Importantly, this implies that the external world cannot make any assumptions about the implementation of the type r. (And if there is no other function in the Instr module, it is impossible to use values of those types).
No, but it is hidden from clients. So run_file.ml cannot make use of the fact that in your implementation files you set the abstract types to unit. In particular Monitor.run_mon expects an arg of type Monitor.t, and that’s all. Clients are not allowed to “know” that monitor.ml defines t as unit.
Try this in the repl:
# module Monitor: sig
type t
val f: unit -> t
end = struct
type t = unit
let f = fun() -> ()
end;;
module Monitor : sig type t val f : unit -> t end
#show Monitor.f;;
val f : unit -> Monitor.t
# Monitor.f ();;
- : Monitor.t = <abstr>
Note that the compiler gets the type of Monitor.f from the signature, not the implementation.