Movatterモバイル変換


[0]ホーム

URL:


Modules

Introduction

In this tutorial, we look at how to use and define modules.

Modules are collections of definitions grouped together. This is the basic means to organise OCaml software. Separate concerns can and should be isolated into separate modules.

Note: The files that illustrate this tutorial are available as a Gitrepo.

Basic Usage

File-Based Modules

In OCaml, every piece of code is wrapped into a module. Optionally, a moduleitself can be asubmodule of another module, pretty much likedirectories in a file system.

Here is a program using two files:athens.ml andberlin.ml. Each filedefines a module namedAthens andBerlin, respectively.

Here is the fileathens.ml:

lethello()=print_endline"Hello from Athens"

Here is the fileberlin.ml:

let()=Athens.hello()

To compile them usingDune, at least twoconfiguration files are required:

  • Thedune-project file contains project-wide configuration.

    (lang dune 3.7)
  • Thedune file contains actual build directives. A project may have severaldune files, one per directory containing things to build. This single line issufficient in this example:

    (executable (name berlin))

After you create those files, build and run them:

$ opamexec -- dune build$ opamexec -- duneexec ./berlin.exeHello from Athens

Note: Dune stores the build artifacts, and a copy of the sources, in the_build directory, where you shall not edit anything. OCaml projects often containbin andlib directories. Unlike in Unix, they don't contain compiled binaries,but source code for programs and libraries.

Actually,opam exec -- dune build is optional. Runningopam exec -- dune exec ./berlin.exe would havetriggered the compilation. Note that in theopam exec -- dune exec command, the parameter./berlin.exe is not a file path. This command means “execute the content ofthe file./berlin.ml.” However, the executable file is stored and nameddifferently.

In a project, it is preferable to create thedune configuration files anddirectory structure using thedune init project command. Refer to the Dunedocumentation for more on this matter.

Naming and Scoping

Inberlin.ml, we usedAthens.hello to refer tohello fromathens.ml.Generally, to access something from a module, use the module's name (whichalways starts with a capital letter:Athens) followed by a dot and thething you want to use (hello). It may be a value, a type constructor, oranything the module provides.

If you are using a module heavily, you might want toopen it. This brings themodule's definitions into scope. In our example,berlin.ml could have beenwritten:

openAthenslet()=hello()

Usingopen is optional. Usually, we don't open a module likeList because itprovides names other modules also provide, such asArray orOption. ModuleslikePrintf provide names that aren't subject to conflicts, such asprintf.Placingopen Printf at the top of a file avoids writingPrintf.printf repeatedly.

openPrintfletdata=["a";"beautiful";"day"]let()=List.iter(printf"%s\n")data

The standard library is a module calledStdlib. It containssubmodulesList,Option,Either, and more. By default, theOCaml compiler opens the standard library, as if you had writtenopen Stdlibat the top of every file. Refer to Dune documentation if you need to opt-out.

You can open a module inside a definition, using thelet open ... in construct:

#letlist_sum_sqm=letopenListininitmFun.id|>map(funi->i*i)|>fold_left(+)0;;vallist_sum_sq:int->int=<fun>

The module access notation can be applied to an entire expression:

#letarray_sum_sqm=Array.(initmFun.id|>map(funi->i*i)|>fold_left(+)0);;valarray_sum_sq:int->int=<fun>

Interfaces and Implementations

By default, anything defined in a module is accessible from other modules.Values, functions, types, or submodules, everything is public. This can berestricted to avoid exposing definitions that are not relevant from the outside.

For this, we must distinguish:

  • The definitions inside a module (the module implementation)
  • The public declarations of a module (the module interface)

An.ml file contains a module implementation; an.mli file contains a moduleinterface. By default, when no corresponding.mli file is provided, animplementation has a default interface where everything is public.

Copy theathens.ml file intocairo.ml and change its contents:

letmessage="Hello from Cairo"lethello()=print_endlinemessage

As it is,Cairo has the following interface:

valmessage:stringvalhello:unit->unit

Explicitly defining a module interface allows restricting the default one. Itacts as a mask over the module's implementation. Thecairo.ml file definesCairo's implementation. Adding acairo.mli file definesCairo's interface.Filenames without extensions must be the same.

To turnmessage into a private definition, don't list it in thecairo.mli file:

valhello:unit->unit(** [hello ()] displays a greeting message.*)

Note: The double asterisk at the beginning indicates acomment meant for API documentation tools, such asodoc. It is a good habit to document.mlifiles using the format supported by this tool.

The filedelhi.ml defines the program callingCairo:

let()=Cairo.hello()

Update thedune file to allow this example's compilation aside from theprevious one.

(executables (names berlin delhi))

Compile and execute both programs:

$ opamexec -- duneexec ./berlin.exeHello from Athens$ opamexec -- duneexec ./delhi.exeHello from Cairo

You can check thatCairo.message is not public by attempting to compile adelhi.ml file containing:

let()=print_endlineCairo.message

This triggers a compilation error.

Abstract and Read-Only Types

Function and value definitions are either public or private. That also appliesto type definitions, but there are two more cases.

Create files namedexeter.mli andexeter.ml with the following contents:

Interface:exeter.mli

typealeph=Ada|Alan|Alonzotypegimelvalgimel_of_bool:bool->gimelvalgimel_flip:gimel->gimelvalgimel_to_string:gimel->stringtypedalet=privateDennisofint|Donaldofstring|Dorothyvaldalet_of:(int,string)Either.toption->dalet

Implementation:exeter.ml

typealeph=Ada|Alan|Alonzotypebet=booltypegimel=Christos|Christineletgimel_of_boolb=if(b:bet)thenChristoselseChristineletgimel_flip=functionChristos->Christine|Christine->Christosletgimel_to_stringx="Christ"^matchxwithChristos->"os"|_->"ine"typedalet=Dennisofint|Donaldofstring|Dorothyletdalet_of=function|None->Dorothy|Some(Either.Leftx)->Dennisx|Some(Either.Rightx)->Donaldx

Update filedune to have three targets; two executables:berlin anddelhi; and a libraryexeter.

(executables (names berlin delhi) (modules athens berlin cairo delhi))(library (name exeter) (modules exeter))

Run theopam exec -- dune utop command. This triggersExeter's compilation, launchesutop, and loadsExeter.

#openExeter;;##showaleph;;typealeph=Ada|Alan|Alonzo

Typealeph is public. Values can be created or accessed.

##showbet;;Unknownelement.

Typebet is private. It is not available outside of the implementation where it is defined, hereExeter.

##showgimel;;typegimel#Christos;;Error:UnboundconstructorChristos##show_valgimel_of_bool;;valgimel_of_bool:bool->gimel#true|>gimel_of_bool|>gimel_to_string;;-:string="Christos"#true|>gimel_of_bool|>gimel_flip|>gimel_to_string;;-:string="Christine"

Typegimel isabstract. Values can be created or manipulated, but only as function results or arguments. Just the provided functionsgimel_of_bool,gimel_flip, andgimel_to_string or polymorphic functions can receive or returngimel values.

##showdalet;;typedalet=privateDennisofint|Donaldofstring|Dorothy#Donald42;;Error:CannotcreatevaluesoftheprivatetypeExeter.dalet#dalet_of(Some(Either.Left10));;-:dalet=Dennis10#letdalet_to_string=function|Dorothy->"Dorothy"|Dennis_->"Dennis"|Donald_->"Donald";;valdalet_to_string:dalet->string=<fun>

The typedalet isread-only. Pattern matching is possible, but values can only be constructed by the provided functions, heredalet_of.

Abstract and read-only types can be either variants, as shown in this section, records, or aliases. It is possible to access a read-only record field's value, but creating such a record requires using a provided function.

Submodules

Submodule Implementation

A module can be defined inside another module. That makes it asubmodule.Let's consider the filesflorence.ml andglasgow.ml

florence.ml

moduleHello=structletmessage="Hello from Florence"letprint()=print_endlinemessageendletprint_goodbye()=print_endline"Goodbye"

glasgow.ml

let()=Florence.Hello.print();Florence.print_goodbye()

Definitions from a submodule are accessed by chaining module names, hereFlorence.Hello.print. Here is the updateddune file, with an additionalexecutable:

dune

(executables (names berlin delhi) (modules athens berlin cairo delhi))(executable (name glasgow) (modules florence glasgow))(library (name exeter) (modules exeter))

Submodule With Signatures

To define a submodule's interface, we can provide amodule signature. Thisis done in this second version of theflorence.ml file:

moduleHello:sigvalprint : unit -> unitend=structletmessage="Hello"letprint()=print_endlinemessageendletprint_goodbye()=print_endline"Goodbye"

The first version madeFlorence.Hello.message public. In this version it can't be accessed fromglasgow.ml.

Module Signatures are Types

The role played by module signatures to implementations is akin to the role played by types to values. Here is a third possible way to write fileflorence.ml:

moduletypeHelloType=sigvalprint : unit -> unitendmoduleHello:HelloType=structletmessage="Hello"letprint()=print_endlinemessageendletprint_goodbye()=print_endline"Goodbye"

First, we define amodule type calledHelloType, which defines the same module interface as before. Instead of providing the signature when defining theHello module, we use theHelloType module type.

This allows writing interfaces shared by several modules. An implementation satisfies any module type listing some of its contents. This implies a module may have several types and that there is a subtyping relationship between module types.

Module Manipulation

Displaying a Module's Interface

You can use the OCaml toplevel to see the contents of an existingmodule, such asUnit:

##showUnit;;moduleUnit:sigtypet = unit = ()valequal : t -> t -> boolvalcompare : t -> t -> intvalto_string : t -> stringend

The OCaml compiler tool chain can be used to dump an.ml file's default interface.

$ ocamlc -i cairo.mlval message: stringval hello: unit -> unit

You can also use Anil Madhavapeddy'socaml-print-intf tool to do the same. You have to install it usingopam install ocaml-print-intf. You can either:

  • Call it on a.cmi file (Compiled ML Interface):ocaml-print-intf cairo.cmi.
  • Call it using Dune:dune exec -- ocaml-print-intf cairo.ml

If you are using Dune,.cmi file are in the_build directory. Otherwise, you can compile manually to generate them. The commandocamlc -c cairo.ml will createcairo.cmo (the executable bytecode) andcairo.cmi (the compiled interface). SeeCompiling OCaml Projects for details on compilation without Dune.

Module Inclusion

Let's say we feel that a function is missing from theList module,but we really want it as if it were part of it. In anextlib.ml file, wecan achieve this effect by using theinclude directive:

moduleList=structincludeStdlib.Listletuncons=function|[]->None|hd::tl->Some(hd,tl)end

It creates a moduleExtlib.List that has everything the standardList modulehas, plus a newuncons function. In order to override the defaultListmodule from another.ml file, we need to addopen Extlib at the beginning.

Stateful Modules

A module may have an internal state. This is the case for theRandom module from the standard library. The functionsRandom.get_state andRandom.set_state provide read and write access to the internal state, which is nameless and has an abstract type.

#lets=Random.get_state();;vals:Random.State.t=<abstr>#Random.bits();;-:int=89809344#Random.bits();;-:int=994326685#Random.set_states;;-:unit=()#Random.bits();;-:int=89809344

Values returned byRandom.bits will differ when you run this code. The firstand third calls return the same results, showing that the internal state wasreset.

Conclusion

In OCaml, modules are the basic means of organising software. To sum up, amodule is a collection of definitions wrapped under a name. These definitionscan be submodules, which allows the creation of hierarchies of modules.Top-level modules must be files and are the units of compilation. Every modulehas an interface, which is the list of definitions a module exposes. By default,a module's interface exposes all its definitions, but this can be restrictedusing the interface syntax.

Going further, here are the other means to handle OCaml software components:

  • Functors, which act like functions from modules to modules
  • Libraries, which are compiled modules bundled together
  • Packages, which are installation and distribution units

Help Improve Our Documentation

All OCaml docs are open source. See something that's wrong or unclear? Submit a pull request.


[8]ページ先頭

©2009-2026 Movatter.jp