I'm converting SML starter code from the Tiger book to OCaml.
What's confusing me is that, in the signature file table.sig
(below), there is no mention of IntMapTable
, but the functor is accessible in another file without any qualification.
(* table.sig *)
signature TABLE =
sig
...
end
(* table.sml *)
functor IntMapTable (...) : TABLE =
struct
...
end
(* symbol.sml *)
...
structure Table = IntMapTable(...)
...
My understanding was that only code in the .sig
file could be accessed to external modules, but not code in the .sml
file. Is this not the case?
Also, what might the equivalent code look like in OCaml? It's awkward, because the functor IntMapTable
's result type is Table
, which is the enclosing module of the file.
With SML, neither files nor their names have much meaning. They are just a way to chop up a larger source into smaller units. A program consisting of multiple files is equivalent to the concatenation of these files in some suitable order.
This is different in OCaml, where every .ml file is itself treated as a structure of a name derived from the file's name, and every associated .mli file, if present, as an opaque signature annotation on that structure.
(Moscow ML, being based on an early implementation of the OCaml runtime, is the one SML implementation that uses a similar file model.)
So when porting over an SML file to OCaml you have two basic choices:
Treat it as if it was wrapped into an extra structure. For example, your
table.sig
could becometable_sig.ml
(not.mli
!) and the signature would be referred to asTable_sig.TABLE
, whiletable.ml
would becometable_fn.ml
with the functor being referred to asTable_fn.IntMapTable
.Sometimes you want to reorganise the file structure a little. For example, instead of having two modules
Table_sig
andTable_fn
, you may choose to have just oneTable
which contains both the signature and the functor. OCaml then tends to use naming conventions like calling the signatureS
and the functorMake
, thus intuitively referring to them asTable.S
andTable.Make
elsewhere.In some cases, you can flatten away this extra wrapper structure. In particular:
a. If the file consists of a single structure declaration whose RHS is a
struct
expression then you can just put its body in an .ml file named after the declared structure.b. If the file consists of a single signature declaration whose RHS is a
sig
expression, and that is only used once to annotate a structure as per case (a), then you can just put its body in an .mli file named after the corresponding structure.The second tends to be the common case.