OCaml Modules Last updated May 4, 2022

Bazel also has modules, which must not be confused with OCaml modules. Bazel modules were introduced after work on OBazl version 2 was well underway so they are not used. The next version will use Bazel modules.

OCaml Modules

An OCaml module is a composite formed by pairing a [signature](#signatures) and a [structure](#structures).

Orphans and Dyads

In OBazl terminology, an orphan module is one whose signature is inferred from its structure. For example, a module explicitly defined within a file module M = struct …​end counts as an orphan module. Similarly, a structfile without a matching sigfile is termed an orphaned structfile, from which an orphan module may be compiled.

A module built from a paired structure and an explicit signature is called a dyadic module. For example, definitions of the following form define a dyadic module: module M : sig …​ end = struct …​ end. A structfile and a sigfile with the same principal name (filename excluding extension, normalized as a module name) together determine a dyadic file-system module.

There is no substantive difference between an orphan module and a dyadic module. The terms are intended for convenience, solely to capture the the two ways that module code can be organized.

Compilation

Compilation of module files always involves separate compilation of interface and implementation; but as a convenience, the OCaml compilers support compilation of stand-alone implementation files. An implicit interface can always be inferred from any implementation file; when passed a .ml file without a corresponding interface, the compilers will extract the implicity defined interface and compile it separately before proceding with the implementation compile. The output of such a compile will thus include a .cmi file in addition to the .cm[ox] output.

Compiling modules - that is, compiling and then binding their interface and implementation files - is straightforward with OBazl. A stand-alone implementation:

ocaml_module(name = "Module1", struct = "module1.ml")

With both parts:

ocaml_signature(name = "Module1_cmi", src = "module1.mli")
ocaml_module(name = "Module1", struct = "module1.ml", sig=":Module1_cmi")

In this example, the sig attribute makes the ocaml_signature target a dependency of the ocaml_module target, which ensures that it will be compiled first, producing module1.cmi. The ocaml_module target will use that .cmi file to govern compilation of module.ml - in effect creating a "module binding" of interface to implementation; its output will include the compiled implementation file(s) (either module1.cmo or the pair module1.cmx and module1.o for native compilation) plus the .cmi file.

This design affords great flexibility in module construction. In particular, it makes it easy to configure module bindings based on user-defined constraints. A typical use case is the need to select a platform-specific implementation file for binding to a platform-independent interface file. No special features or concepts (e.g. "virtual library") are needed for this; the built-in select function suffices. Here is an example based on code in the OCaml compiler sources (with some detail omitted):

asmcomp/BUILD.bazel
ocaml_signature(
    name = "Proc_cmi",
    src  = "proc.mli"
)
ocaml_module(
    name   = "Proc",
    struct = select({
        "//config/arch:x86_64": "//asmcomp/amd64:proc.ml",
        ... etc. ...
        "//config/arch:s390x"   : "//asmcomp/s390x:proc.ml",
    }, no_match_error = "unknown arch"),
    sig    = "Proc_cmi"
)

The selection constraints are defined in a separate file:

config/arch/BUILD.bazel
config_setting(name = "x86_64",
               constraint_values = ["@platforms//cpu:x86_64"])
... other conf_setting targets omitted ...
config_setting(name = "s390x",
               constraint_values = ["@platforms//cpu:s390x"])

In this example, the implementation file will be selected based on the platform (e.g. @platforms//cpu:x86_64); the ocaml_module rule will ensure that the selected file will be compiled using proc.cmi as its interface.

  • [Overview](#overview) module binding

sec 2

ocaml_signature(name = "Hello_cmi", src = "hello.mli")
ocaml_module(name = "Hello", struct = "hello.ml", sig = ":Hello_cmi")

sec 3

Module types

Module Type Definitions (Cornell book)

Bazel Modules

Expected for version 3

test images

Static