Your First OCaml Program
To complete this tutorial, you need to haveinstalled OCaml. Optionally, we recommendconfiguring your editor.
We will work with files containing OCaml source code and compile them to produce executable binaries. However, this is not a detailed tutorial on OCaml compilation, project modularisation, or dependencies management; it only gives a glimpse at those topics. The goal is to sketch the bigger picture in order to avoid getting lost in the details. In other words, we do breadth-first learning instead of depth-first learning.
In the previous tutorial most commands were entered in UTop. In this tutorial, the majority of commands should be entered into a terminal. Code examples starting with a dollar sign$ are intended to be entered in the terminal, while lines starting with a hash sign# are intended to be entered in UTop.
Once you've completed this tutorial, you should be able to create, compile, and execute an OCaml project using Dune, OCaml's build system. You will be able to work with files, make private definitions within modules, and know how to install and use opam packages.
Note: The files illustrating this tutorial are available as aGit repo.
Working Within an opam Switch
When you installed OCaml, a global opam switch was created automatically. This tutorial can be completed while working inside this global opam switch.
When you work on several OCaml projects simultaneously, you should create more opam switches. For instructions on how to do that, seeIntroduction to opam Switches.
Compiling OCaml Programs
By default, OCaml comes with two compilers: one translating sources into native binaries and another turning sources into a bytecode format. OCaml also comes with an interpreter for that bytecode format. This tutorial demonstrates how to use the native compiler to write OCaml programs.
We start by setting up a traditional “Hello, World!” project using Dune. Make sure to have installed version 3.12 or later. The following creates a project namedhello:
$ opamexec -- dune init proj helloSuccess: initialized project component named helloNote 1: If you have runeval $(opam env) at the start of your current terminal session, or if you answered yes to the question that was asked when you ranopam init, you can omitopam exec -- from the start ofdune commands.
Note 2: Throughout this tutorial, outputs generated by Dune might vary slightly because of the Dune version installed. This tutorial shows the output for Dune 3.12. If you'd like to get the most recent version of Dune, runopam update; opam upgrade dune in a terminal.
The project is stored in a directory namedhello with the following contents:
hello├── bin│ ├── dune│ └── main.ml├── _build│ └── log├── dune-project├── hello.opam├── lib│ └── dune└──test ├── dune └── hello.mlUnlike in Unix where they contain compiled binaries, directorieslib andbin contain source code files, for libraries and programs, respectively. This is the convention used in many OCaml projects, including those created by Dune. All the built artifacts, and a copy of the sources, are stored in the_build directory. Do not edit anything in the_build directory, since any manual edits will be overwritten during subsequent builds.
OCaml source files have the.ml extension, which stands for “Meta Language.” Meta Language (ML) is an ancestor of OCaml. This is also what the “ml” stands for in “OCaml.” Here is the content of thebin/main.ml file:
let()=print_endline"Hello, World!"The project-wide metadata is available in thedune-project file. It contains information about the project name, dependencies, and global setup.
Each directory containing source files that need to be built must contain adune file describing how.
This builds the project:
opamexec -- dune buildThis launches the executable it creates:
$ opamexec -- duneexec helloHello, World!Let's see what happens when we edit thebin/main.ml file directly. Open it in your editor and replace the wordWorld with your first name. Recompile the project withdune build as before, and then launch it again withdune exec hello.
Voilà! You've just written your first OCaml program.
In the rest of this tutorial, we will make more changes to this project in order to illustrate OCaml's tooling.
Watch Mode
Before we dive in, note that you will typically want to use Dune's watch mode to continually compile and optionally restart your program. This ensures that the language server has the freshest possible data about your project, so your editor support will be top-notch. To use watch mode, just add the-w flag:
opamexec -- dune build -wopamexec -- duneexec hello -wWhy Isn't There a Main Function?
Althoughbin/main.ml's name suggests it contains the application entry point into the project, it does not contain a dedicatedmain function, and there is no requirement that a project must contain a file with that name in order to produce an executable. A compiled OCaml file behaves as though that file were entered line by line into the toplevel. In other words, an executable OCaml file's entry point is its first line.
Double semicolons aren't needed in source files like they are in the toplevel. Statements are just processed in order from top to bottom, each triggering the side effects it may have. Definitions are added to the environment. Values resulting from nameless expressions are ignored. Side effects from all those will take place in the same order. That's OCaml main.
However, it is common practice to single out a value that triggers all the side effects and mark it as the intended main entry point. In OCaml, that's the role oflet () =, which evaluates the expression on the right, including all the side effects, without creating a name.
Modules and the Standard Library, Cont'd
Let's summarise what was said about modules in theTour of OCaml:
- A module is a collection of named values.
- Identical names from distinct modules don't clash.
- The standard library is a collection of several modules.
Modules aid in organising projects; concerns can be separated into isolated modules. This is outlined in the next section. Before creating a module ourselves, we'll demonstrate using a definition from a module of the standard library. Change the content of the filebin/main.ml to this:
let()=Printf.printf"%s\n""Hello, World!"This replaces the functionprint_endline with the functionprintf from thePrintf module in the standard library. Building and executing this modified version should produce the same output as before. Usedune exec hello to try it for yourself.
Every File Defines a Module
Each OCaml file defines a module, once compiled. This is how separate compilation works in OCaml. Each sufficiently standalone concern should be isolated into a module. References to external modules create dependencies. Circular dependencies between modules are not allowed.
To create a module, let's create a new file namedlib/en.ml containing this:
letv="Hello, world!"Here is a new version of thebin/main.ml file:
let()=Printf.printf"%s\n"Hello.En.vNow execute the resulting project:
$ opamexec -- duneexec helloHello, world!The filelib/en.ml creates the module namedEn, which in turn defines a string value namedv. Dune wrapsEn into another module calledHello; this name is defined by the stanzaname hello in the filelib/dune. The string definition isHello.En.v from thebin/main.ml file.
Dune can launch UTop to access the modules exposed by a project interactively. Here's how:
opamexec -- dune utopThen, inside theutop toplevel, it is possible to inspect ourHello.En module:
##showHello.En;;moduleHello:sigvalv : stringendNow exitutop withCtrl-D or enter#quit;; before going to the next section.
Note: If you add a file namedhello.ml in thelib directory, Dune will consider this the wholeHello module and it will makeEn unreachable. If you want your moduleEn to be visible, you need to add this in yourhello.ml file:
moduleEn=EnDefining Module Interfaces
UTop's#show command displays anAPI (in the software library sense): the list of definitions provided by a module. In OCaml, this is called amodule interface. An.ml file defines a module. In a similar way, an.mli file defines a module interface. The module interface file corresponding to a module file must have the same base name, e.g.,en.mli is the module interface for moduleen.ml. Create alib/en.mli file with this content:
valv:stringObserve that only the list of the module signature's declarations (which is betweensig andend in the#show output) has been written in the interface filelib/en.mli. This is explained in more detail in the tutorial dedicated tomodules.
Module interfaces are also used to createprivate definitions. A module definition is private if it is not listed in its corresponding module interface. If no module interface file exists, everything is public.
Amend thelib/en.ml file in your preferred editor; replace what's there with the following:
lethello="Hello"letv=hello^", world!"Also edit thebin/main.ml file like this:
let()=Printf.printf"%s\n"Hello.En.helloTrying to compile this fails.
$ opamexec -- dune buildFile"hello/bin/main.ml", line 1, characters 30-43:1|let() = Printf.printf"%s\n" Hello.En.hello ^^^^^^^^^^^^^^Error: Unbound value Hello.En.helloThis is because we haven't changedlib/en.mli. Since it does not listhello, it is therefore private.
Defining Multiple Modules in a Library
Multiple modules can be defined in a single library. To demonstrate this,create a new file namedlib/es.ml with the following content:
letv="¡Hola, mundo!"And use the new module inbin/main.ml:
let()=Printf.printf"%s\n"Hello.Es.vlet()=Printf.printf"%s\n"Hello.En.vFinally, rundune build anddune exec hello to see the new output, using the modulesyou just created in thehello library.
$ opamexec -- duneexec hello¡Hola, mundo!Hello, world!A more detailed introduction to modules can be found atModules.
Installing and Using Modules From a Package
OCaml has an active community of open-source contributors. Most projects are available using the opam package manager, which you installed in theInstall OCaml tutorial. The following section shows you how to install and use a package from opam's open-source repository.
To illustrate this, let's update ourhello project to parse a string containing anS-expression and print back to a string, both usingSexplib. First, update the package list for opam by runningopam update. Then, install theSexplib package with this command:
opam install sexplibNext, define a string containing a valid S-expression inbin/main.ml. Parseit into an S-expression with theSexplib.Sexp.of_string function, and thenconvert it back into a string withSexplib.Sexp.to_string and print it.
(* Read in Sexp from string*)letexp1=Sexplib.Sexp.of_string"(This (is an) (s expression))"(* Do something with the Sexp ...*)(* Convert back to a string to print*)let()=Printf.printf"%s\n"(Sexplib.Sexp.to_stringexp1)The string you entered representing a valid S-expression is parsed intoan S-expression type, which is defined as either anAtom (string) or aListof S-expressions (it's a recursive type). Refer to theSexplib documentation for more information.
Before the example will build and run, you need to tell Dune that it needsSexplib to compile the project. Do this by addingSexplib to thelibrary stanza of thebin/dune file. The fullbin/dune file should then match the following.
(executable (public_name hello) (name main) (libraries hello sexplib))Fun fact: Dune configuration files are S-expressions.
Finally, execute as before:
$ opamexec -- duneexec hello(()())Using the Preprocessor to Generate Code
Note: This example was successfully tested on Windows using DkML 2.1.0. Rundkml version to see the version.
Let's assume we'd likehello to display its output as if it was a list of strings in UTop:["hello"; "using"; "an"; "opam"; "library"]. To do that, we need a function turning astring list into astring, adding brackets, spaces, and commas. Instead of defining it ourselves, let's generate it automatically with a package. We'll useppx_deriving. Here is how to install it:
opam install ppx_derivingDune needs to be told how to use it, which is done in thelib/dune file. Note that this is different from thebin/dune file that you edited earlier! Open up thelib/dune file, and edit it to look like this:
(library (name hello) (preprocess (pps ppx_deriving.show)))The line(preprocess (pps ppx_deriving.show)) means that before compilation the source needs to be transformed using the preprocessorshow provided by the packageppx_deriving. It is not required to write(libraries ppx_deriving), Dune infers that from thepreprocess stanza.
The fileslib/en.ml andlib/en.mli need to be edited, too:
lib/en.mli
valstring_of_string_list:stringlist->stringvalv:stringlistlib/en.ml
letstring_of_string_list=[%show:stringlist]letv=String.split_on_char' '"Hello using an opam library"Let's read this from the bottom up:
vhas the typestring list. We're usingString.split_on_charto turn astringinto astring listby splitting the string on space characters.string_of_string_listhas typestring list -> string. This converts a list of strings into a string, applying the expected formatting.
Finally, you'll also need to editbin/main.ml
let()=print_endlineHello.En.(string_of_string_listv)Here is the result:
$ opamexec -- duneexec hello["Hello";"using";"an";"opam";"library"]A Sneak-Peek at Dune as a One-Stop Shop
This section explains the purpose of the files and directories created bydune init proj which haven't been mentioned earlier.
Along the history of OCaml, several build systems have been used. As of writing this tutorial (Summer 2023), Dune is the mainstream one, which is why it is used in the tutorial. Dune automatically extracts the dependencies between the modules from the files and compiles them in a compatible order. It only needs onedune file per directory where there is something to build. The three directories created bydune init proj have the following purposes:
bin: executable programslib: librariestest: tests
There will be a tutorial dedicated to Dune. This tutorial will present the many features of Dune, a few of which are listed here:
- Running tests
- Generating documentation
- Producing packaging metadata (here in
hello.opam) - Creating arbitrary files using all-purpose rules
The_build directory is where Dune stores all the files it generates. It can be deleted at any time, but subsequent builds will recreate it.
Minimum Setup
In this last section, let's create a bare minimum project, highlighting what's really needed for Dune to work. We begin by creating a fresh project directory:
cd ..mkdir minimocd minimoAt the very least, Dune only needs two files:dune-project and onedune file. Here is how to write them with as little text as possible:
dune-project
(lang dune 3.6)dune
(executable (name minimo))minimo.ml
let()=print_endline"My name is Minimo"That's all! This is sufficient for Dune to build and execute theminimo.ml file.
$ opamexec -- duneexec ./minimo.exeMy name is MinimoNote:minimo.exe is not a file name. This is how Dune is told to compile theminimo.ml file using OCaml's native compiler instead of the bytecode compiler. As a fun fact, note that an empty file is valid OCaml syntax. You can use that to reduceminimo even more; of course, it will not display anything, but it will be a valid project!
Conclusion
This tutorial is the last of the "Getting Started" series. Moving forward, you have enough to pick and choose among the other tutorials to follow your own learning path.
Help Improve Our Documentation
All OCaml docs are open source. See something that's wrong or unclear? Submit a pull request.
- Working Within an opam Switch
- Compiling OCaml Programs
- Watch Mode
- Why Isn't There a Main Function?
- Modules and the Standard Library, Cont'd
- Every File Defines a Module
- Defining Module Interfaces
- Defining Multiple Modules in a Library
- Installing and Using Modules From a Package
- Using the Preprocessor to Generate Code
- A Sneak-Peek at Dune as a One-Stop Shop
- Minimum Setup
- Conclusion
[8]ページ先頭