When we're working with modules, there are some subtleties that you need to be aware of. When you use #use in utop, you're essentially copying the entire contents of the file into utop.
module type TestSig = sig
type t
val f : t -> t
val sample_value : t
end
module IntF = struct
type t = int
let f = succ
let sample_value = 8;
end
module IntFSealed : TestSig = IntF
module type TestSig = sig type t val f : t -> t val sample_value : t end
module IntF : sig type t = int val f : int -> int val sample_value : int end
module IntFSealed : TestSig
IntF.sample_value;;
IntFSealed.sample_value;;
- : int = 8
- : IntFSealed.t = <abstr>
Notice how utop prints out the value as <abstr>, rather than 8. This is because utop is unaware of the implementation of IntFSealed; however, it is aware of the implementation of IntF, as it is unsealed.
Recall that utop's #use directive is as if we directly typed the code into utop. This means that it doesn't have any concept of the interface file. On the other hand, if we use ocamlbuild, it uses the entire compilation unit.
If we use #directory "_build" to enter the ocamlbuild directory, and then #load the .cmo file, this is the equivalent of how it works.
You can also specify a .ocamlinit file, which will run on the launch of utop.