0% found this document useful (0 votes)
84 views523 pages

Ocaml Programming

sml programming

Uploaded by

shallumanning
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
84 views523 pages

Ocaml Programming

sml programming

Uploaded by

shallumanning
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 523

OCaml Programming: Correct +

Efficient + Beautiful

Michael R. Clarkson et al.

May 16, 2024


CONTENTS

I Preface 3
1 About This Book 5

2 Installing OCaml 7
2.1 Unix Development Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Install OPAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Initialize OPAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Create an OPAM Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.5 Double-Check OCaml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.6 Visual Studio Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.7 Double-Check VS Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.8 VS Code Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.9 Using VS Code Collaboratively . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

II Introduction 15
3 Better Programming Through OCaml 17
3.1 The Past of OCaml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.2 The Present of OCaml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3 Look to Your Future . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4 A Brief History of CS 3110 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 The Basics of OCaml 23


4.1 The OCaml Toplevel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2 Compiling OCaml Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.4 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.5 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.6 Printing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.7 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

III OCaml Programming 71


5 Data and Types 73
5.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2 Variants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

i
5.3 Unit Testing with OUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.4 Records and Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.5 Advanced Pattern Matching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.6 Type Synonyms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.7 Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.8 Association Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.9 Algebraic Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.10 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
5.11 Example: Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
5.12 Example: Natural Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
5.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
5.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

6 Higher-Order Programming 131


6.1 Higher-Order Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6.2 Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.3 Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.4 Fold . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.5 Beyond Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
6.6 Pipelining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
6.7 Currying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
6.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
6.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

7 Modular Programming 161


7.1 Module Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
7.2 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.3 Modules and the Toplevel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
7.4 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
7.5 Compilation Units . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
7.6 Functional Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
7.7 Module Type Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
7.8 Includes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
7.9 Functors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
7.10 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
7.11 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

IV Correctness and Efficiency 247


8 Correctness 249
8.1 Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
8.2 Function Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
8.3 Module Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
8.4 Testing and Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
8.5 Black-box and Glass-box Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
8.6 Randomized Testing with QCheck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
8.7 Proving Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
8.8 Structural Induction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
8.9 Algebraic Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
8.10 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
8.11 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311

9 Mutability 317
9.1 Refs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

ii
9.2 Mutable Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
9.3 Arrays and Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
9.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
9.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338

10 Data Structures 343


10.1 Hash Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
10.2 Amortized Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
10.3 Red-Black Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
10.4 Sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
10.5 Memoization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
10.6 Promises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
10.7 Monads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
10.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
10.9 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415

V Language Implementation 427


11 Interpreters 429
11.1 Example: Calculator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
11.2 Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
11.3 Substitution Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
11.4 Environment Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
11.5 Type Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
11.6 Type Inference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
11.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
11.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485

VI Lagniappe 495
12 The Curry-Howard Correspondence 497
12.1 Computing with Evidence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
12.2 The Correspondence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
12.3 Types Correspond to Propositions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
12.4 Programs Correspond to Proofs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
12.5 Evaluation Corresponds to Simplification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
12.6 What It All Means . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
12.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505

VII Appendix 507


13 Big-Oh Notation 509
13.1 Algorithms and Efficiency, Attempt 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
13.2 Algorithms and Efficiency, Attempt 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
13.3 Big-Ell Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
13.4 Big-Oh Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
13.5 Big-Oh, Finished . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
13.6 Big-Oh Notation Warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
13.7 Algorithms and Efficiency, Attempt 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513

14 Virtual Machine 515


14.1 Installing the VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515

iii
14.2 Starting the VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
14.3 Stopping the VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
14.4 Using the VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516

iv
OCaml Programming: Correct + Efficient + Beautiful

A textbook on functional programming and data structures in OCaml, with an emphasis on semantics and software en-
gineering. This book is the textbook for CS 3110 Data Structures and Functional Programming at Cornell University. A
past title of this book was “Functional Programming in OCaml”.
Spring 2024 Edition.
Videos. There are over 200 YouTube videos embedded in this book. They can be watched independently of reading the
book. Start with this YouTube playlist.
Authors. This book is based on courses taught by Michael R. Clarkson, Robert L. Constable, Nate Foster, Michael D.
George, Dan Grossman, Justin Hsu, Daniel P. Huttenlocher, Dexter Kozen, Greg Morrisett, Andrew C. Myers, Radu
Rugina, and Ramin Zabih. Together they have created over 20 years worth of course notes and intellectual contributions.
Teasing out who contributed what is, by now, not an easy task. The primary compiler and author of this work in its form
as a unified textbook is Michael R. Clarkson, who as of the Fall 2021 edition was the author of about 40% of the words
and code tokens.
Copyright 2021–2024 Michael R. Clarkson. Released under the Creative Commons Attribution-NonCommercial-
NoDerivatives 4.0 International License.

CONTENTS 1
OCaml Programming: Correct + Efficient + Beautiful

2 CONTENTS
Part I

Preface

3
CHAPTER

ONE

ABOUT THIS BOOK

Reporting Errors. If you find an error, please report it! Or if you have a suggestion about how to rewrite some part of
the book, let us know. Just go to the page of the book for which you’d like to make a suggestion, click on the GitHub icon
(it looks like a cat) near the top right of the page, and click “open issue” or “suggest edit”. The latter is a little heavier
weight, because it requires you to fork the textbook repository with GitHub. But for minor edits that will be appreciated
and lead to much quicker uptake of suggestions.
Background. This book is used at Cornell for a third-semester programming course. Most students have had one
semester of introductory programming in Python, followed by one semester of object-oriented programming in Java.
Frequent comparisons are therefore made to those two languages. Readers who have studied similar languages should
have no difficulty following along. The book does not assume any prior knowledge of functional programming, but it
does assume that readers have prior experience programming in some mainstream imperative language. Knowledge of
discrete mathematics at the level of a standard first-semester CS course is also assumed.
Videos. You will find over 200 YouTube videos embedded throughout this book. The videos usually provide an introduc-
tion to material, upon which the textbook then expands. These videos were produced during pandemic when the Cornell
course that uses this textbook, CS 3110, had to be asynchronous. The student response to them was overwhelmingly
positive, so they are now being made public as part of the textbook. But just so you know, they were not produced by a
professional A/V team—just a guy in his basement who was learning as he went.
The videos mostly use the versions of OCaml and its ecosystem that were current in Fall 2020. Current versions you
are using are likely to look different from the videos, but don’t be alarmed: the underlying ideas are the same. The most
visible difference is likely to be the VS Code plugin for OCaml. In Fall 2020 the badly-aging “OCaml and Reason IDE”
plugin was still being used. It has since been superseded by the “OCaml Platform” plugin.
The textbook and videos sometimes cover topics in different orders. The videos are placed in the textbook nearest to the
topic they cover. To watch the videos in their original order, start with this YouTube playlist.
Collaborative Annotations. At the right margin of each page, you will find an annotation feature provided by hypothes.is.
You can use this to highlight and make private notes as you study the text. You can form study groups to share your
annotations, or share them publicly. Check out these tips for how to annotate effectively.
Executable Code. Many pages of this book have OCaml code embedded in them. The output of that code is already
shown in the book. Here’s an example:

print_endline "Hello world!"

Hello world!

- : unit = ()

You can also edit and re-run the code yourself to experiment and check your understanding. Look for the icon near the
top right of the page that looks like a rocket ship. In the drop-down menu you’ll find two ways to interact with the code:

5
OCaml Programming: Correct + Efficient + Beautiful

• Binder will launch the site mybinder.org, which is a free cloud-based service for “reproducible, interactive, shareable
environments for science at scale.” All the computation happens in their cloud servers, but the UI is provided
through your browser. It will take a little while for the textbook page to open in Binder. Once it does, you can
edit and run the code in a Jupyter notebook. Jupyter notebooks are documents (usually ending in the .ipynb
extension) that can be viewed in web browsers and used to write narrative content as well as code. They became
popular in data science communities (especially Python, R, and Julia) as a way of sharing analyses. Now many
languages can run in Jupyter notebooks, including OCaml. Code and text are written in cells in a Jupyter notebook.
Look at the “Cell” menu in it for commands to run cells. Note that Shift-Enter is usually a hotkey for running the
cell that has focus.
• Live Code will actually do about the same thing, except that instead of leaving the current textbook page and taking
you off to Binder, it will modify the code cells on the page to be editable. It takes some time for the connection to
be made behind the scenes, during which you will see “Waiting for kernel”. After the connection has been made,
you can edit all the code cells on the page and re-run them. If the connection fails, then first launch the Binder
site; this can take a long time. After it succeeds and loads the textbook page as a Jupyter notebook, you can close
Binder, reload the textbook page, and launch Live Code again. It should now be successful at connecting relatively
quickly.
Try interacting with the cell above now to make it print a string of your choice. How about: "Camels are bae."

Tip: When you write “real” OCaml code, this is not the interface you’ll be using. You’ll write code in an editor such
as Visual Studio Code or Emacs, and you’ll compile it from a terminal. Binder and Live Code are just for interacting
seamlessly with the textbook.

Downloadable Pages. Each page of this book is downloadable in a variety of formats. The download icon is at the top
right of each page. You’ll always find the original source code of the page, which is usually Markdown—or more precisely
MyST Markdown, which is an extension of Markdown for technical writing. Each page is also individually available as
PDF, which simply prints from your browser. For the entire book as a PDF, see the paragraph about that below.
Pages with OCaml code cells embedded in them can also be downloaded as Jupyter notebooks. To run those locally on
your own machine (instead of in the cloud on Binder), you’ll need to install Jupyter. The easiest way of doing that is
typically to install Anaconda. Then you’ll need to install OCaml Jupyter, which requires that you already have OCaml
installed. To be clear, there’s no need to install Jupyter or to use notebooks. It’s just another way to interact with this
textbook beyond reading it.
Exercises and Solutions. At the end of each chapter except the first, you will find a section of exercises. The exercises
are annotated with a difficulty rating:
• One star [★]: easy exercises that should take only a minute or two.
• Two stars [★★]: straightforward exercises that should take a few minutes.
• Three stars [★★★]: exercises that might require anywhere from five to twenty minutes or so.
• Four [★★★★] or more stars: challenging or time-consuming exercises provided for students who want to dig
deeper into the material.
It’s possible we’ve misjudged the difficulty of a problem from time to time. Let us know if you think an annotation is off.
Please do not post your solutions to the exercises anywhere, especially not in public repositories where they could be found
by search engines. Solutions to most exercises are available. Fall 2022 is the first public release of these solutions. Though
they have been available to Cornell students for a few years, it is inevitable that wider circulation will reveal improvements
that could be made. We are happy to add or correct solutions. Please make contributions through GitHub.
PDF. A full PDF version of this book is available. It does not contain the embedded videos, annotations, or other features
that the HTML version has. It might also have typesetting errors. At this time, no tablet (ePub, etc.) version is available,
but most tablets will let you import PDFs.

6 Chapter 1. About This Book


CHAPTER

TWO

INSTALLING OCAML

If all you need is a way to follow along with the code examples in this book, you don’t actually have to install OCaml!
The code on each page is executable in your browser, as described earlier in this Preface.
If you want to take it a step further but aren’t ready to spend time installing OCaml yourself, we provide a virtual machine
with OCaml pre-installed inside a Linux OS.
But if you want to do OCaml development on your own, you’ll need to install it on your machine. There’s no universally
“right” way to do that. The instructions below are for Cornell’s CS 3110 course, which has goals and needs beyond just
OCaml. Nonetheless, you might find them to be useful even if you’re not a student in the course.
Here’s what we’re going to install:
• A Unix development environment
• OPAM, the OCaml Package Manager
• An OPAM switch with the OCaml compiler and some packages
• The Visual Studio Code editor, with OCaml support
The installation process will rely heavily on the terminal, or text interface to your computer. If you’re not too familiar
with it, you might want to brush up with a terminal tutorial.

Tip: If this is your first time installing development software, it’s worth pointing out that “close doesn’t count”: trying
to proceed past an error usually just leads to worse errors, and sadness. That’s because we’re installing a kind of tower
of software, with each level of the tower building on the previous. If you’re not building on a solid foundation, the whole
thing might collapse. The good news is that if you do get an error, you’re probably not alone. A quick google search
will often turn up solutions that others have discovered. Of course, do think critically about suggestions made by random
strangers on the internet.

Let’s get started!

2.1 Unix Development Environment

Important: First, upgrade your OS. If you’ve been intending to make any major OS upgrades, do them now. Otherwise
when you do get around to upgrading, you might have to repeat some or all of this installation process. Better to get it out
of the way beforehand.

7
OCaml Programming: Correct + Efficient + Beautiful

2.1.1 Linux

If you’re already running Linux, you’re done with this step. Proceed to Install OPAM, below.

2.1.2 Mac

Beneath the surface, macOS is already a Unix-based OS. But you’re going to need some developer tools and a Unix
package manager. There are two to pick from: Homebrew and MacPorts. From the perspective of this textbook and CS
3110, it doesn’t matter which you choose:
• If you’re already accustomed to one, feel free to keep using it. Make sure to run its update command before
continuing with these instructions.
• Otherwise, pick one and follow the installation instructions on its website. The installation process for Homebrew is
typically easier and faster, which might nudge you in that direction. If you do choose MacPorts, make sure to follow
all the detailed instructions on its page, including XCode and an X11 server. Do not install both Homebrew and
MacPorts; they aren’t meant to co-exist. If you change your mind later, make sure to uninstall one before installing
the other.
After you’ve finished installing/updating either Homebrew or MacPorts, proceed to Install OPAM, below.

2.1.3 Windows

Unix development in Windows is made possible by the Windows Subsystem for Linux (WSL). If you have a recent version
of Windows (build 20262, released November 2020, or newer), WSL is easy to install. If you don’t have that recent of a
version, try running Windows Update to get it.

Tip: If you get an error about the “virtual machine” while installing WSL, you might need to enable virtualization in
your machine’s BIOS. The instructions for that are dependent on the manufacturer of your machine. Try googling “enable
virtualization [manufacturer] [model]”, substituting for the manufacturer and model of your machine. This Red Hat Linux
page might also help.

With a recent version of Windows, and assuming you’ve never installed WSL before, here’s all you have to do:
• Open Windows PowerShell as Administrator. To do that, click Start, type PowerShell, and it should come up as
the best match. Click “Run as Administrator”, and click Yes to allow changes.
• Run wsl --install. (Or, if you have already installed WSL but not Ubuntu before, then instead run wsl
--install -d Ubuntu.) When the Ubuntu download is completed, it will likely ask you to reboot. Do so.
The installation will automatically resume after the reboot.
• You will be prompted to create a Unix username and password. You can use any username and password you wish.
It has no bearing on your Windows username and password (though you are free to re-use those). Do not put a
space in your username. Do not forget your password. You will need it in the future.

Warning: Do not proceed with these instructions if you were not prompted to create a Unix username and password.
Something has gone wrong. Perhaps your Ubuntu installation did not complete correctly. Try uninstalling Ubuntu
and reinstalling it through the Windows Start menu.

Now skip to the “Ubuntu setup” paragraph below.

8 Chapter 2. Installing OCaml


OCaml Programming: Correct + Efficient + Beautiful

Without a recent version of Windows, you will need to follow Microsoft’s manual installation instructions. WSL2 is
preferred over WSL1 by OCaml (and WSL2 offers performance and functionality improvements), so install WSL2 if you
can.
Ubuntu setup. These rest of these instructions assume that you installed Ubuntu (22.04) as the Linux distribution. That
is the default distribution in WSL. In principle other distributions should work, but might require different commands
from this point forward.
Open the Ubuntu app. (It might already be open if you just finished installing WSL.) You will be at the Bash prompt,
which looks something like this:

user@machine:~$

Warning: If that prompt instead looks like root@...#, something is wrong. Did you create a Unix username and
password for Ubuntu in the earlier step above? If so, the username in this prompt should be the username you chose
back then, not root. Do not proceed with these instructions if your prompt looks like root@...#. Perhaps you
could uninstall Ubuntu and reinstall it.

In the current version of the Windows terminal, Ctrl+Shift+C will copy and Ctrl+Shift+V will paste into the terminal.
Note that you have to include Shift as part of that keystroke. In older versions of the terminal, you might need to find an
option in the terminal settings to enable those keyboard shortcuts.
Run the following command to update the APT package manager, which is what helps to install Unix packages:

sudo apt update

You will be prompted for the Unix password you chose. The prefix sudo means to run the command as the administrator,
aka “super user”. In other words, do this command as super user, hence, “sudo”.

Warning: Running commands with sudo is potentially dangerous and should not be done lightly. Do not get into
the habit of putting sudo in front of commands, and do not randomly try it without reason.

Now run this command to upgrade all the APT software packages:

sudo apt upgrade -y

Then install some useful packages that we will need:

sudo apt install -y zip unzip build-essential

File Systems. WSL has its own filesystem that is distinct from the Windows file system, though there are ways to access
each from the other.
• When you launch Ubuntu and get the $ prompt, you are in the WSL file system. Your home directory there is
named ~, which is a built-in alias for /home/your_ubuntu_user_name. You can run explorer.exe .
(note the dot at the end of that) to open your Ubuntu home directory in Windows explorer.
• From Ubuntu, you can access your Windows home directory at the path /mnt/c/Users/
your_windows_user_name/.
• From Windows Explorer, you can access your Ubuntu home directory under the Linux icon in the left-hand
list (near “This PC” and “Network”), then navigating to Ubuntu → home → your_ubuntu_user_name.
Or you can go there directly by typing into the Windows Explorer path bar: \\wsl$\Ubuntu\home\
your_ubuntu_user_name.

2.1. Unix Development Environment 9


OCaml Programming: Correct + Efficient + Beautiful

Practice accessing your Ubuntu and Windows home directories now, and make sure you can recognize which you are in.
For advanced information, see Microsoft’s guide to Windows and Linux file systems.
We recommend storing your OCaml development work in your Ubuntu home directory, not your Windows home direc-
tory. By implication, Microsoft also recommends that in the guide just linked.

2.2 Install OPAM

Linux. Follow the instructions for your distribution.


Mac. If you’re using Homebrew, run this command:

brew install opam

If you’re using MacPorts, run this command:

sudo port install opam

Windows. Run this command from Ubuntu:

sudo apt install opam

2.3 Initialize OPAM

Warning: Do not put sudo in front of any opam commands. That would break your OCaml installation.

Linux, Mac, and WSL2. Run:

opam init --bare -a -y

(Don’t worry if you get a note about making sure .profile is “well-sourced” in .bashrc. You don’t need to do
anything about that.)
WSL1. Hopefully you are running WSL2, not WSL1. But on WSL1, run:

opam init --bare -a -y --disable-sandboxing

It is necessary to disable sandboxing because of an issue involving OPAM and WSL1.

2.4 Create an OPAM Switch

A switch is a named installation of OCaml with a particular compiler version and set of packages. You can have many
switches and, well, switch between them —whence the name. Create a switch for this semester’s CS 3110 by running this
command:

opam switch create cs3110-2024sp ocaml-base-compiler.5.1.1

10 Chapter 2. Installing OCaml


OCaml Programming: Correct + Efficient + Beautiful

Tip: If that command fails saying that the 5.1.1 compiler can’t be found, you probably installed OPAM sometime back
in the past and now need to update it. Do so with opam update.

You might be prompted to run the next command. It won’t matter whether you do or not, because of the very next step
we’re going to do (i.e., logging out).

eval $(opam env)

Now we need to make sure your OCaml environment was configured correctly. Logout from your OS (or just reboot).
Then re-open your terminal and run this command:

opam switch list

You should get output like this:

# switch compiler description


→ cs3110-2024sp ocaml-base-compiler.5.1.1 cs3110-2024sp

There might be other lines if you happen to have done OCaml development before. Here’s what to check for:
• You must not get a warning that “The environment is not in sync with the current switch. You should run eval
$(opam env)”. If either of the two issues below also occur, you need to resolve this issue first.
• There must be a right arrow in the first column next to the cs3110-2024sp switch.
• That switch must have the right name and the right compiler version, 5.1.1.

Warning: If you do get that warning about opam env, something is wrong. Your shell is probably not running the
OPAM configuration commands that opam init was meant to install. You could try opam init --reinit
to see whether that fixes it. Also, make sure you really did log out of your OS (or reboot).

Continue by installing the OPAM packages we need:

opam install -y utop odoc ounit2 qcheck bisect_ppx menhir ocaml-lsp-server ocamlformat

Make sure to grab that whole line above when you copy it. You will get some output about editor configuration. Unless
you intend to use Emacs or Vim for OCaml development, you can safely ignore that output. We’re going to use VS Code
as the editor in these instructions, so let’s ignore it.
You should now be able to launch utop, the OCaml Universal Toplevel.

utop

Tip: You should see a message “Welcome to utop version … (using OCaml version 5.1.1)!” If the OCaml version is
incorrect, then you probably have an environment issue. See the tip above about the opam env command.

Enter 3110 followed by two semicolons. Press return. The # is the utop prompt; you do not type it yourself.

# 3110;;
- : int = 3110

2.4. Create an OPAM Switch 11


OCaml Programming: Correct + Efficient + Beautiful

Stop to appreciate how lovely 3110 is. Then quit utop. Note that this time you must enter the extra # before the quit
directive.

# #quit;;

A faster way to quit is to type Control+D.

2.5 Double-Check OCaml

If you’re having any trouble with your installation, follow these double-check instructions. Some of them repeat the tips
we provided above, but we’ve put them all here in one place to help diagnose any issues.
First, reboot your computer. We need a clean slate for this double-check procedure.
Second, run utop, and make sure it works. If it does not, here are some common issues:
• Are you in the right Unix prompt? On Mac, make sure you are in whatever Unix shell is the default for your
Terminal: don’t run bash or zsh or anything else manually to change the shell. On Windows, make sure you are in
the Ubuntu app, not PowerShell or Cmd.
• Is the OPAM environment set? If utop isn’t a recognized command, run eval $(opam env) then try
running utop again. If utop now works, your login shell is somehow not running the right commands to automatically
activate the OPAM environment; you shouldn’t have to manually activate the environment with the eval command.
Probably something went wrong earlier when you ran the opam init command. To fix it, follow the “redo”
instructions below.
• Is your switch listed? Run opam switch list and make sure a switch named cs3110-2024sp is listed,
that it has the 5.1.1 compiler, and that it is the active switch (which is indicated with an arrow beside it). If that
switch is present but not active, run opam switch cs3110-2024sp then see whether utop works. If that
switch is not present, follow the “redo” instructions below.
Redo Instructions: Remove the OPAM directory by running rm -r ~/.opam. Then go back to the OPAM initial-
ization step in the instructions way above, and proceed forward. Be extra careful to use the exact OPAM commands given
above; sometimes mistakes occur when parts of them are omitted. Finally, double-check again: reboot and see whether
utop still works.

Important: You want to get to the point where utop immediately works after a reboot, without having to type any
additional commands.

2.6 Visual Studio Code

Visual Studio Code is a great choice as a code editor for OCaml. (Though if you are already a power user of Emacs or
Vim those are great, too.)
First, download and install Visual Studio Code (henceforth, VS Code). Launch VS Code. Open the extensions pane,
either by going to View → Extensions, or by clicking on the icon for it in the column of icons on the left — it looks like
four little squares, the top-right of which is separated from the other three.
At various points in the following instructions you will be asked to “open the Command Palette.” To do that, go to View
→ Command Palette. There is also an operating system specific keyboard shortcut, which you will see to the right of the
words “Command Palette” in that View menu.
Second, follow one of these steps if you are on Windows or Mac:

12 Chapter 2. Installing OCaml


OCaml Programming: Correct + Efficient + Beautiful

• Windows only: Install the “WSL” extension.


• Mac only: Open the Command Palette and type “shell command” to find the “Shell Command: Install ‘code’
command in PATH” command. Run it.
Third, regardless of your OS, close any open terminals — or just logout or reboot — to let the new path settings take
effect, so that you will later be able to launch VS Code from the terminal.
Fourth, on Windows only, open the Command Palette and run the command “WSL: Connect to WSL”. (If you on Mac,
skip ahead to the next step.) The first time you do this, it will install some additional software. After that completes,
you will see a “WSL: Ubuntu” indicator in the bottom-left of the VS Code window. Make sure that you see “WSL:
Ubuntu” there before proceeding with the next step below. If you see just an icon that looks like >< then click it, and
choose “Connect to WSL” from the Command Palette that opens.
Fifth, again open the VS Code extensions pane. Search for and install the “OCaml Platform” extension from OCaml
Labs. Be careful to install the extension with exactly that name.

Warning: The extensions named simply “OCaml” or “OCaml and Reason IDE” are not the right ones. They are
both old and no longer maintained by their developers.

2.7 Double-Check VS Code

Let’s make sure VS Code’s OCaml support is working.


• Reboot your computer again. (Yeah, that really shouldn’t be necessary. But it will detect so many potential mistakes
now that it’s worth the effort.)
• Open a fresh new Unix shell. Windows: remember that’s the Ubuntu, not PowerShell or Cmd. Mac: remember
that you shouldn’t be manually switching to a different shell by typing zsh or bash.
• Navigate to a directory of your choice, preferably a subdirectory of your home directory. For example, you might
create a directory for your 3110 work inside your home directory:

mkdir ~/3110
cd ~/3110

In that directory open VS Code by running:

code .

Go to File → New File. Save the file with the name test.ml. VS Code should give it an orange camel icon.
• Type the following OCaml code then press Return/Enter:

let x : int = 3110

As you type, VS Code should colorize the syntax, suggest some completions, and add a little annotation above the
line of code. Try changing the int you typed to string. A squiggle should appear under 3110. Hover over
it to see the error message. Go to View → Problems to see it there, too. Add double quotes around the integer to
make it a string, and the problem will go away.
If you don’t observe those behaviors, something is wrong with your installation. Here’s how to proceed:
• Make sure that, from the same Unix prompt as which you launched VS Code, you can successfully complete the
double-check instructions for your OPAM switch: Can you run utop? Is the right switch active? If not, that’s the
problem you need to solve first. Then return to the VS Code issue. It might be fixed now.

2.7. Double-Check VS Code 13


OCaml Programming: Correct + Efficient + Beautiful

• If you’re on WSL and VS Code does add syntax highlighting but does not add squiggles as described above, and/or
you get an error about “Sandbox initialization failed”, then double-check that you see a “WSL” indicator in the
bottom left of the VS Code window. If you do not, make sure you installed the “WSL” extension as described
above, and that you are launching VS Code from Ubuntu rather than PowerShell or from the Windows GUI. If you
do, make sure that the “OCaml Platform” extension is installed.
If you’re still stuck with an issue, try uninstalling VS Code, rebooting, and re-doing all the installation instructions
above from scratch. Pay close attention to any warnings or errors.

Warning: While troubleshooting any VS Code issues, do not hardcode any paths in the VS Code settings file,
despite any advice you might find online. That is a band-aid, not a cure of whatever the underlying problem really is.
More than likely, the real problem is an OCaml environment issue that you can investigate with the OCaml double-
check instructions above.

2.8 VS Code Settings

We recommend tweaking a few editor settings. Open the user settings JSON file by (i) going to View → Command
Palette, (ii) typing “user settings json”, and (iii) selecting Open User Settings (JSON). Copy and paste these settings into
the window:

{
"editor.tabSize": 2,
"editor.rulers": [ 80 ],
"editor.formatOnSave": true
}

Save the file and close the tab.

2.9 Using VS Code Collaboratively

VS Code’s Live Share extension makes it easy and fun to collaborate on code with other humans. You can edit code
together like collaborating inside a Google Doc. It even supports a shared voice channel, so there’s no need to spin up a
separate Zoom call. To install and use Live Share, follow Microsoft’s tutorial.
If you are a Cornell student, log in with your Microsoft account, not GitHub. Enter your Cornell NetID email, e.g.,
[email protected]. That will take you to Cornell’s login site. Use the password associated with your
NetID.

14 Chapter 2. Installing OCaml


Part II

Introduction

15
CHAPTER

THREE

BETTER PROGRAMMING THROUGH OCAML

Do you already know how to program in a mainstream language like Python or Java? Good. This book is for you. It’s
time to learn how to program better. It’s time to learn a functional language, OCaml.
Functional programming provides a different perspective on programming than what you have experienced so far. Adapt-
ing to that perspective requires letting go of old ideas: assignment statements, loops, classes and objects, among others.
That won’t be easy.
Nan-in (2/72/7), a Japanese master during the Meiji era (1868-1912), received a university professor who came
to inquire about Zen. Nan-in served tea. He poured his visitor’s cup full, and then kept on pouring. The
professor watched the overflow until he no longer could restrain himself. “It is overfull. No more will go in!”
“Like this cup,” Nan-in said, “you are full of your own opinions and speculations. How can I show you Zen
unless you first empty your cup?”
I believe that learning OCaml will make you a better programmer. Here’s why:
• You will experience the freedom of immutability, in which the values of so-called “variables” cannot change. Good-
bye, debugging.
• You will improve at abstraction, which is the practice of avoiding repetition by factoring out commonality. Goodbye,
bloated code.
• You will be exposed to a type system that you will at first hate because it rejects programs you think are correct. But
you will come to love it, because you will humbly realize it was right and your programs were wrong. Goodbye,
failing tests.
• You will be exposed to some of the theory and implementation of programming languages, helping you to understand
the foundations of what you are saying to the computer when you write code. Goodbye, mysterious and magic
incantations.
All of those ideas can be learned in other contexts and languages. But OCaml provides an incredible opportunity to bundle
them all together. OCaml will change the way you think about programming.
“A language that doesn’t affect the way you think about programming is not worth knowing.”
---Alan J. Perlis (1922-1990), first recipient of the Turing Award
Moreover, OCaml is beautiful. OCaml is elegant, simple, and graceful. Aesthetics do matter. Code isn’t written just to
be executed by machines. It’s also written to communicate to humans. Elegant code is easier to read and maintain. It isn’t
necessarily easier to write.
The OCaml code you write can be stylish and tasteful. At first, this might not be apparent. You are learning a new
language after all—you wouldn’t expect to appreciate Sanskrit poetry on day 1 of Introductory Sanskrit. In fact, you’ll
likely feel frustrated for a while as you struggle to express yourself in a new language. So give it some time. After you’ve
mastered OCaml, you might be surprised at how ugly those other languages you already know end up feeling when you
return to them.

17
OCaml Programming: Correct + Efficient + Beautiful

3.1 The Past of OCaml

Genealogically, OCaml comes from the line of programming languages whose grandfather is Lisp and includes other
modern languages such as Clojure, F#, Haskell, and Racket.
OCaml originates from work done by Robin Milner and others at the Edinburgh Laboratory for Computer Science in
Scotland. They were working on theorem provers in the late 1970s and early 1980s. Traditionally, theorem provers were
implemented in languages such as Lisp. Milner kept running into the problem that the theorem provers would sometimes
put incorrect “proofs” (i.e., non-proofs) together and claim that they were valid. So he tried to develop a language that
only allowed you to construct valid proofs. ML, which stands for “Meta Language”, was the result of that work. The
type system of ML was carefully constructed so that you could only construct valid proofs in the language. A theorem
prover was then written as a program that constructed a proof. Eventually, this “Classic ML” evolved into a full-fledged
programming language.
In the early ’80s, there was a schism in the ML community with the French on one side and the British and US on another.
The French went on to develop CAML and later Objective CAML (OCaml) while the Brits and Americans developed
Standard ML. The two dialects are quite similar. Microsoft introduced its own variant of OCaml called F# in 2005.
Milner received the Turing Award in 1991 in large part for his work on ML. The ACM website for his award includes
this praise:
ML was way ahead of its time. It is built on clean and well-articulated mathematical ideas, teased apart so
that they can be studied independently and relatively easily remixed and reused. ML has influenced many
practical languages, including Java, Scala, and Microsoft’s F#. Indeed, no serious language designer should
ignore this example of good design.

3.2 The Present of OCaml

OCaml is a functional programming language. The key linguistic abstraction of functional languages is the mathematical
function. A function maps an input to an output; for the same input, it always produces the same output. That is,
mathematical functions are stateless: they do not maintain any extra information or state that persists between usages of
the function. Functions are first-class: you can use them as input to other functions, and produce functions as output.
Expressing everything in terms of functions enables a uniform and simple programming model that is easier to reason
about than the procedures and methods found in other families of languages.
Imperative programming languages such as C and Java involve mutable state that changes throughout execution. Com-
mands specify how to compute by destructively changing that state. Procedures (or methods) can have side effects that
update state in addition to producing a return value.
The fantasy of mutability is that it’s easy to reason about: the machine does this, then this, etc.
The reality of mutability is that whereas machines are good at complicated manipulation of state, humans are not good
at understanding it. The essence of why that’s true is that mutability breaks referential transparency: the ability to replace
an expression with its value without affecting the result of a computation. In math, if 𝑓(𝑥) = 𝑦, then you can substitute
𝑦 anywhere you see 𝑓(𝑥). In imperative languages, you cannot: 𝑓 might have side effects, so computing 𝑓(𝑥) at time 𝑡
might result in a different value than at time 𝑡′ .
It’s tempting to believe that there’s a single state that the machine manipulates, and that the machine does one thing at a
time. Computer systems go to great lengths in attempting to provide that illusion. But it’s just that: an illusion. In reality,
there are many states, spread across threads, cores, processors, and networked computers. And the machine does many
things concurrently. Mutability makes reasoning about distributed state and concurrent execution immensely difficult.
Immutability, however, frees the programmer from these concerns. It provides powerful ways to build correct and con-
current programs. OCaml is primarily an immutable language, like most functional languages. It does support imperative
programming with mutable state, but we won’t use those features until many chapters into the book—in part because we

18 Chapter 3. Better Programming Through OCaml


OCaml Programming: Correct + Efficient + Beautiful

simply won’t need them, and in part to get you to quit “cold turkey” from a dependence you might not have known that
you had. This freedom from mutability is one of the biggest changes in perspective that OCaml can give you.

3.2.1 The Features of OCaml

OCaml is a statically-typed and type-safe programming language. A statically-typed language detects type errors at com-
pile time; if a type error is detected, the language won’t allow execution of the program. A type-safe language limits
which kinds of operations can be performed on which kinds of data. In practice, this prevents a lot of silly errors (e.g.,
treating an integer as a function) and also prevents a lot of security problems: over half of the reported break-ins at the
Computer Emergency Response Team (CERT, a US government agency tasked with cybersecurity) were due to buffer
overflows, something that’s impossible in a type-safe language.
Some languages, like Python and Racket, are type-safe but dynamically typed. That is, type errors are caught only at run
time. Other languages, like C and C++, are statically typed but not type safe: they check for some type errors, but don’t
guarantee the absence of all type errors. That is, there’s no guarantee that a type error won’t occur at run time. And still
other languages, like Java, use a combination of static and dynamic typing to achieve type safety.
OCaml supports a number of advanced features, some of which you will have encountered before, and some of which
are likely to be new:
• Algebraic data types: You can build sophisticated data structures in OCaml easily, without fussing with pointers
and memory management. Pattern matching—a feature we’ll soon learn about that enables examining the shape of
a data structure—makes them even more convenient.
• Type inference: You do not have to write type information down everywhere. The compiler automatically figures
out most types. This can make the code easier to read and maintain.
• Parametric polymorphism: Functions and data structures can be parameterized over types. This is crucial for
being able to re-use code.
• Garbage collection: Automatic memory management relieves you from the burden of memory allocation and
deallocation, a common source of bugs in languages such as C.
• Modules: OCaml makes it easy to structure large systems through the use of modules. Modules are used to
encapsulate implementations behind interfaces. OCaml goes well beyond the functionality of most languages with
modules by providing functions (called functors) that manipulate modules.

3.2.2 OCaml in Industry

OCaml and other functional languages are nowhere near as popular as Python, C, or Java. OCaml’s real strength lies in
language manipulation (i.e., compilers, analyzers, verifiers, provers, etc.). This is not surprising, because OCaml evolved
from the domain of theorem proving.
That’s not to say that functional languages aren’t used in industry. There are many industry projects using OCaml and
Haskell, among other languages. Yaron Minsky (Cornell PhD ‘02) even wrote a paper about using OCaml in the financial
industry. It explains how the features of OCaml make it a good choice for quickly building complex software that works.

3.2. The Present of OCaml 19


OCaml Programming: Correct + Efficient + Beautiful

3.3 Look to Your Future

General-purpose languages come and go. In your life you’ll likely learn a handful. Today, it’s Python and Java. Yesterday,
it was Pascal and Cobol. Before that, it was Fortran and Lisp. Who knows what it will be tomorrow? In this fast-
changing field you need to be able to rapidly adapt. A good programmer has to learn the principles behind programming
that transcend the specifics of any specific language. There’s no better way to get at these principles than to approach
programming from a functional perspective. Learning a new language from scratch affords the opportunity to reflect along
the way about the difference between programming and programming in a language.
If after OCaml you want to learn more about functional programming, you’ll be well prepared. OCaml does a great job
of clarifying and simplifying the essence of functional programming in a way that other languages that blend functional
and imperative programming (like Scala) or take functional programming to the extreme (like Haskell) do not.
And even if you never code in OCaml again after learning it, you’ll still be better prepared for the future. Advanced
features of functional languages have a surprising tendency to predict new features of more mainstream languages. Java
brought garbage collection into the mainstream in 1995; Lisp had it in 1958. Java didn’t have generics until version 5 in
2004; the ML family had it in 1990. First-class functions and type inference have been incorporated into mainstream
languages like Java, C#, and C++ over the last 10 years, long after functional languages introduced them.

News Flash!
Python just announced plans to support pattern matching in February 2021.

3.4 A Brief History of CS 3110

This book is the primary textbook for CS 3110 at Cornell University. The course has existed for over two decades and
has always taught functional programming, but it has not always used OCaml.
Once upon a time, there was a course at MIT known as 6.001 Structure and Interpretation of Computer Programs (SICP).
It had a textbook by the same name, and it used Scheme, a functional programming language. Tim Teitelbaum taught a
version of the course at Cornell in Fall 1988, following the book rather closely and using Scheme.
CS 212. Dan Huttenlocher had been a TA for 6.001 at MIT; he later became faculty at Cornell. In Fall 1989, he
inaugurated CS 212 Modes of Algorithm Expression. Basing the course on SICP, he infused a more rigorous approach
to the material. Huttenlocher continued to develop CS 212 through the mid 1990s, using various homegrown dialects of
Scheme.
Other faculty began teaching the course regularly. Ramin Zabih had taken 6.001 as a first-year student at MIT. In Spring
1994, having become faculty at Cornell, he taught CS 212. Dexter Kozen (Cornell PhD 1977) first taught the course
in Spring 1996. The earliest surviving online record of the course seems to be Spring 1998, which was taught by Greg
Morrisett in Dylan; the name of the course had become Structure and Interpretation of Computer Programs.
By Fall 1999, CS 212 had its own lecture notes. As CS 3110 still does, that instance of CS 212 covered functional
programming, the substitution and environment models, some data structures and algorithms, and programming language
implementation.
CS 312. At that time, the CS curriculum had two introductory programming courses, CS 211 Computers and Program-
ming, and CS 212. Students took one or the other, similar to how students today take either CS 2110 or CS 2112. Then
they took CS 410 Data Structures. The earliest surviving online record of CS 410 seems to be from Spring 1998. It
covered many data structures and algorithms not covered by CS 212, including balanced trees and graphs, and it used
Java as the programming language.
Depending on which course they took, CS 211 or 212, students were entering upper-level courses with different skill sets.
After extensive discussions, the faculty chose to make CS 211 required, to rename CS 212 into CS 312 Data Structures

20 Chapter 3. Better Programming Through OCaml


OCaml Programming: Correct + Efficient + Beautiful

and Functional Programming, and to make CS 211 a prerequisite for CS 312. At the same time, CS 410 was eliminated
from the curriculum and its contents parceled out to CS 312 and CS 482 Introduction to Analysis of Algorithms. Dexter
Kozen taught the final offering of CS 410 in Fall 1999.
Greg Morrisett inaugurated the new CS 312 in Spring 2001. He switched from Scheme to Standard ML. Kozen first
taught it in Fall 2001, and Andrew Myers in Fall 2002. Myers began to incorporate material on modular programming
from another MIT textbook, Program Development in Java: Abstraction, Specification, and Object-Oriented Design by
Barbara Liskov and John Guttag. Huttenlocher first taught the course in Spring 2006.
CS 3110. In Fall 2008 two big changes came: the language switched to OCaml, and the university switched to four-digit
course numbers. CS 312 became CS 3110. Myers, Huttenlocher, Kozen, and Zabih first taught the revised course in Fall
2008, Spring 2009, Fall 2009, and Fall 2010, respectively. Nate Foster first taught the course in Spring 2012; and Bob
Constable and Michael George co-taught for the first time in Fall 2013.
Michael Clarkson (Cornell PhD 2010) first taught the course in Fall 2014, after having first TA’d the course as a PhD
student back in Spring 2008. He began to revise the presentation of the OCaml programming material to incorporate ideas
by Dan Grossman (Cornell PhD 2003) about a principled approach to learning a programming language by decomposing
it into syntax, dynamic, and static semantics. Grossman uses that approach in CSE 341 Programming Languages at the
University of Washington and in his popular Programming Languages MOOC.
In Fall 2018 the compilation of this textbook began. It synthesizes the work of over two decades of functional program-
ming instruction at Cornell. In the words of the Cornell Evening Song,
‘Tis an echo from the walls Of our own, our fair Cornell.

3.5 Summary

This book is about becoming a better programmer. Studying functional programming will help with that. The biggest
obstacle in our way is the frustration of speaking a new language, particularly letting go of mutable state. But the benefits
will be great: a discovery that programming transcends programming in any particular language or family of languages,
an exposure to advanced language features, and an appreciation of beauty.

3.5.1 Terms and Concepts

• dynamic typing
• first-class functions
• functional programming languages
• immutability
• Lisp
• ML
• OCaml
• referential transparency
• side effects
• state
• static typing
• type safety

3.5. Summary 21
OCaml Programming: Correct + Efficient + Beautiful

3.5.2 Further Reading

• Introduction to Objective Caml, chapters 1 and 2, a freely available textbook that is recommended for this course
• OCaml from the Very Beginning, chapter 1, a textbook that is very gentle and recommended for this course. The
PDF and HTML formats of the book are free of charge.
• A guided tour [of OCaml]: chapter 1 of Real World OCaml, a book written by some Cornellians that some students
might enjoy reading
• The history of Standard ML: though it focuses on the SML variant of the ML language, it’s relevant to OCaml
• The value of values: a lecture by the designer of Clojure (a modern dialect of Lisp) on how the time of imperative
programming has passed
• Teach yourself programming in 10 years: an essay by a Director of Research at Google that puts the time required
to become an educated programmer into perspective

22 Chapter 3. Better Programming Through OCaml


CHAPTER

FOUR

THE BASICS OF OCAML

This chapter will cover some of the basic features of OCaml. But before we dive in to learning OCaml, let’s first talk
about a bigger idea: learning languages in general.
One of the secondary goals of this course is not just for you to learn a new programming language, but to improve your
skills at learning how to learn new languages.
There are five essential components to learning a language: syntax, semantics, idioms, libraries, and tools.
Syntax. By syntax, we mean the rules that define what constitutes a textually well-formed program in the language,
including the keywords, restrictions on whitespace and formatting, punctuation, operators, etc. One of the more annoying
aspects of learning a new language can be that the syntax feels odd compared to languages you already know. But the
more languages you learn, the more you’ll become used to accepting the syntax of the language for what it is, rather than
wishing it were different. (If you want to see some languages with really unusual syntax, take a look at APL, which needs
its own extended keyboard, and Whitespace, in which programs consist entirely of spaces, tabs, and newlines.) You need
to understand syntax just to be able to speak to the computer at all.
Semantics. By semantics, we mean the rules that define the behavior of programs. In other words, semantics is about
the meaning of a program—what computation a particular piece of syntax represents. Note that although “semantics” is
plural in form, we use it as singular. That’s similar to “mathematics” or “physics”.
There are two pieces to semantics, the dynamic semantics of a language and the static semantics of a language. The
dynamic semantics define the run-time behavior of a program as it is executed or evaluated. The static semantics define
the compile-time checking that is done to ensure that a program is legal, beyond any syntactic requirements. The most
important kind of static semantics is probably type checking: the rules that define whether a program is well-typed or not.
Learning the semantics of a new language is usually the real challenge, even though the syntax might be the first hurdle
you have to overcome. You need to understand semantics to say what you mean to the computer, and you need to say
what you mean so that your program performs the right computation.
Idioms. By idioms, we mean the common approaches to using language features to express computations. Given that
you might express one computation in many ways inside a language, which one do you choose? Some will be more
natural than others. Programmers who are fluent in the language will prefer certain modes of expression over others. We
could think of this in terms of using the dominant paradigms in the language effectively, whether they are imperative,
functional, object-oriented, etc. You need to understand idioms to say what you mean not just to the computer, but to
other programmers. When you write code idiomatically, other programmers will understand your code better.
Libraries. Libraries are bundles of code that have already been written for you and can make you a more productive
programmer, since you won’t have to write the code yourself. (It’s been said that laziness is a virtue for a programmer.)
Part of learning a new language is discovering what libraries are available and how to make use of them. A language
usually provides a standard library that gives you access to a core set of functionality, much of which you would be unable
to code up in the language yourself, such as file I/O.
Tools. At the very least any language implementation provides either a compiler or interpreter as a tool for interacting with
the computer using the language. But there are other kinds of tools: debuggers; integrated development environments
(IDE); and analysis tools for things like performance, memory usage, and correctness. Learning to use tools that are
associated with a language can also make you a more productive programmer. Sometimes it’s easy to confuse the tool

23
OCaml Programming: Correct + Efficient + Beautiful

itself for the language; if you’ve only ever used Eclipse and Java together for example, it might not be apparent that Eclipse
is an IDE that works with many languages, and that Java can be used without Eclipse.
When it comes to learning OCaml in this book, our focus is primarily on semantics and idioms. We’ll have to learn syntax
along the way, of course, but it’s not the interesting part of our studies. We’ll get some exposure to the OCaml standard
library and a couple other libraries, notably OUnit (a unit testing framework similar to JUnit, HUnit, etc.). Besides the
OCaml compiler and build system, the main tool we’ll use is the toplevel, which provides the ability to interactively
experiment with code.

4.1 The OCaml Toplevel

The toplevel is like a calculator or command-line interface to OCaml. It’s similar to JShell for Java, or the interactive
Python interpreter. The toplevel is handy for trying out small pieces of code without going to the trouble of launching
the OCaml compiler. But don’t get too reliant on it, because creating, compiling, and testing large programs will require
more powerful tools. Some other languages would call the toplevel a REPL, which stands for read-eval-print-loop: it reads
programmer input, evaluates it, prints the result, and then repeats.
In a terminal window, type utop to start the toplevel. Press Control-D to exit the toplevel. You can also enter #quit;;
and press return. Note that you must type the # there: it is in addition to the # prompt you already see.

4.1.1 Types and values

You can enter expressions into the OCaml toplevel. End an expression with a double semicolon ;; and press the return
key. OCaml will then evaluate the expression, tell you the resulting value, and the value’s type. For example:

# 42;;
- : int = 42

Let’s dissect that response from utop, reading right to left:


• 42 is the value.
• int is the type of the value.
• The value was not given a name, hence the symbol -.
That utop interaction was “hardcoded” as part of this book. We had to type in all the characters: the #, the -, etc. But the
infrastructure used to write this book actually enables us to write code that is evaluated by OCaml at the time the book is
translated into HTML or PDF. From now on, that’s usually what we will do. It looks like this:

42

- : int = 42

The first code block with the 42 in it is the code we asked OCaml to run. If you want to enter that into utop, you can copy
and paste it. There’s an icon in the top right of the block to do that easily. Just remember to add the double semicolon at
the end. The second code block, which is indented a little, is the output from OCaml as the book was being translated.

Tip: If you’re viewing this in a web browser, look to the top right for a download icon. Choose the .md option, and
you’ll see the original MyST Markdown source code for this page of the book. You’ll see that the output from the second
example above is not actually present in the source code. That’s good! It means that the output stays consistent with

24 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

whatever current version of the OCaml compiler we use to build the book. It also means that any compilation errors can
be detected as part of building the book, instead of lurking for you, dear reader, to find them.

You can bind values to names with a let definition, as follows:

let x = 42

val x : int = 42

Again, let’s dissect that response, this time reading left to right:
• A value was bound to a name, hence the val keyword.
• x is the name to which the value was bound.
• int is the type of the value.
• 42 is the value.
You can pronounce the entire output as “x has type int and equals 42.”

4.1.2 Functions

A function can be defined at the toplevel using syntax like this:

let increment x = x + 1

val increment : int -> int = <fun>

Let’s dissect that response:


• increment is the identifier to which the value was bound.
• int -> int is the type of the value. This is the type of functions that take an int as input and produce an int
as output. Think of the arrow -> as a kind of visual metaphor for the transformation of one value into another
value—which is what functions do.
• The value is a function, which the toplevel chooses not to print (because it has now been compiled and has a
representation in memory that isn’t easily amenable to pretty printing). Instead, the toplevel prints <fun>, which
is just a placeholder.

Note: <fun> itself is not a value. It just indicates an unprintable function value.

You can “call” functions with syntax like this:

increment 0

- : int = 1

increment(21)

- : int = 22

4.1. The OCaml Toplevel 25


OCaml Programming: Correct + Efficient + Beautiful

increment (increment 5)

- : int = 7

But in OCaml the usual vocabulary is that we “apply” the function rather than “call” it.
Note how OCaml is flexible about whether you write the parentheses or not, and whether you write whitespace or not.
One of the challenges of first learning OCaml can be figuring out when parentheses are actually required. So if you find
yourself having problems with syntax errors, one strategy is to try adding some parentheses. The preferred style, though,
is usually to omit parentheses when they are not needed. So, increment 21 is better than increment(21).

4.1.3 Loading code in the toplevel

In addition to allowing you to define functions, the toplevel will also accept directives that are not OCaml code but rather
tell the toplevel itself to do something. All directives begin with the # character. Perhaps the most common directive
is #use, which loads all the code from a file into the toplevel, just as if you had typed the code from that file into the
toplevel.
For example, suppose you create a file named mycode.ml. In that file put the following code:

let inc x = x + 1

Start the toplevel. Try entering the following expression, and observe the error:

inc 3

File "[7]", line 1, characters 0-3:


1 | inc 3
^^^
Error: Unbound value inc
Hint: Did you mean incr?

The error occurs because the toplevel does not yet know anything about a function named inc. Now issue the following
directive to the toplevel:

# #use "mycode.ml";;

Note that the first # character above indicates the toplevel prompt to you. The second # character is one that you type
to tell the toplevel that you are issuing a directive. Without that character, the toplevel would think that you are trying to
apply a function named use.
Now try again:

inc 3

- : int = 4

26 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

4.1.4 Workflow in the toplevel

The best workflow when using the toplevel with code stored in files is:
• Edit the code in the file.
• Load the code in the toplevel with #use.
• Interactively test the code.
• Exit the toplevel. Warning: do not skip this step.

Tip: Suppose you wanted to fix a bug in your code. It’s tempting to not exit the toplevel, edit the file, and re-issue
the #use directive into the same toplevel session. Resist that temptation. The “stale code” that was loaded from an
earlier #use directive in the same session can cause surprising things to happen—surprising when you’re first learning
the language, anyway. So always exit the toplevel before re-using a file.

4.2 Compiling OCaml Programs

Using OCaml as a kind of interactive calculator can be fun, but we won’t get very far with writing large programs that
way. We instead need to store code in files and compile them.

4.2.1 Storing code in files

Open a terminal, create a new directory, and open VS Code in that directory. For example, you could use the following
commands:

$ mkdir hello-world
$ cd hello-world

Warning: Do not use the root of your Unix home directory as the place you store the file. The build system we
are going to use very soon, dune, might not work right in the root of your home directory. Instead, you need to use a
subdirectory of your home directory.

Use VS Code to create a new file named hello.ml. Enter the following code into the file:

let _ = print_endline "Hello world!"

Note: There is no double semicolon ;; at the end of that line of code. The double semicolon is intended for interactive
sessions in the toplevel, so that the toplevel knows you are done entering a piece of code. There’s usually no reason to
write it in a .ml file.

The let _ = above means that we don’t care to give a name (hence the “blank” or underscore) to code on the right-hand
side of the =.
Save the file and return to the command line. Compile the code:

$ ocamlc -o hello.byte hello.ml

4.2. Compiling OCaml Programs 27


OCaml Programming: Correct + Efficient + Beautiful

The compiler is named ocamlc. The -o hello.byte option says to name the output executable hello.byte.
The executable contains compiled OCaml bytecode. In addition, two other files are produced, hello.cmi and hello.
cmo. We don’t need to be concerned with those files for now. Run the executable:

$ ./hello.byte

It should print Hello world! and terminate.


Now change the string that is printed to something of your choice. Save the file, recompile, and rerun. Try making the
code print multiple lines.
This edit-compile-run cycle between the editor and the command line is something that might feel unfamiliar if you’re
used to working inside IDEs like Eclipse. Don’t worry; it will soon become second nature.
Now let’s clean up all those generated files:

$ rm hello.byte hello.cmi hello.cmo

4.2.2 What about Main?

Unlike C or Java, OCaml programs do not need to have a special function named main that is invoked to start the
program. The usual idiom is just to have the very last definition in a file serve as the main function that kicks off whatever
computation is to be done.

4.2.3 Dune

In larger projects, we don’t want to run the compiler or clean up manually. Instead, we want to use a build system to
automatically find and link in libraries. OCaml has a legacy build system called ocamlbuild, and a newer build system
called Dune. Similar systems include make, which has long been used in the Unix world for C and other languages; and
Gradle, Maven, and Ant, which are used with Java.
A Dune project is a directory (and its subdirectories) that contain OCaml code you want to compile. The root of a project
is the highest directory in its hierarchy. A project might rely on external packages providing additional code that is already
compiled. Usually, packages are installed with OPAM, the OCaml Package Manager.
Each directory in your project can contain a file named dune. That file describes to Dune how you want the code in
that directory (and subdirectories) to be compiled. Dune files use a functional-programming syntax descended from LISP
called s-expressions, in which parentheses are used to show nested data that form a tree, much like HTML tags do. The
syntax of Dune files is documented in the Dune manual.

Creating a Dune Project Manually

Here is a small example of how to use Dune. In the same directory as hello.ml, create a file named dune and put the
following in it:

(executable
(name hello))

That declares an executable (a program that can be executed) whose main file is hello.ml.
Also create a file named dune-project and put the following in it:

(lang dune 3.4)

28 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

That tells Dune that this project uses Dune version 3.4, which was current at the time this version of the textbook was
released. This project file is needed in the root directory of every source tree that you want to compile with Dune. In
general, you’ll have a dune file in every subdirectory of the source tree but only one dune-project file at the root.
Then run this command from the terminal:

$ dune build hello.exe

Note that the .exe extension is used on all platforms by Dune, not just on Windows. That causes Dune to build a native
executable rather than a bytecode executable.
Dune will create a directory _build and compile our program inside it. That’s one benefit of the build system over
directly running the compiler: instead of polluting your source directory with a bunch of generated files, they get cleanly
created in a separate directory. Inside _build there are many files that get created by Dune. Our executable is buried a
couple of levels down:

$ _build/default/hello.exe
Hello world!

But Dune provides a shortcut to having to remember and type all of that. To build and execute the program in one step,
we can simply run:

$ dune exec ./hello.exe


Hello world!

Finally, to clean up all the compiled code we just run:

$ dune clean

That removes the _build directory, leaving just your source code.

Tip: When Dune compiles your program, it caches a copy of your source files in _build/default. If you ever
accidentally make a mistake that results in loss of a source file, you might be able to recover it from inside _build. Of
course, using source control like git is also advisable.

Warning: Do not edit any of the files in the _build directory. If you ever get an error about trying to save a file
that is read-only, you maybe are attempting to edit a file in the _build directory.

Creating a Dune Project Automatically

In the terminal, change to a directory where you want to store your work, for example, “~/work”. Pick a name for your
project, such as “calculator”. Run:

$ dune init project calculator


$ cd calculator
$ code .

You should now have VS Code open and see the files that Dune automatically generated for your project.
From the terminal in the calculator directory, run:

4.2. Compiling OCaml Programs 29


OCaml Programming: Correct + Efficient + Beautiful

$ dune exec bin/main.exe

It will print Hello, World!

Tip: If you use ocamlformat to automatically format your source code, note that Dune does not add a .ocamlformat
file to your project automatically. You might want to add one in the top-level directory, aka the root, of your project. That
is the directory that has the file named dune-project in it.

Running Dune Continuously

When you run dune build, it compiles your project once. You might want to have your code compiled automatically
every time you save a file in your project. To accomplish that, run this command:

$ dune build --watch

Dune will respond that it is waiting for filesystem changes. That means Dune is now running continuously and rebuilding
your project every time you save a file in VS Code. To stop Dune, press Control+C.

4.3 Expressions

The primary piece of OCaml syntax is the expression. Just like programs in imperative languages are primarily built out
of commands, programs in functional languages are primarily built out of expressions. Examples of expressions include
2+2 and increment 21.
The OCaml manual has a complete definition of all the expressions in the language. Though that page starts with a rather
cryptic overview, if you scroll down, you’ll come to some English explanations. Don’t worry about studying that page
now; just know that it’s available for reference.
The primary task of computation in a functional language is to evaluate an expression to a value. A value is an expression
for which there is no computation remaining to be performed. So, all values are expressions, but not all expressions are
values. Examples of values include 2, true, and "yay!".
The OCaml manual also has a definition of all the values, though again, that page is mostly useful for reference rather
than study.
Sometimes an expression might fail to evaluate to a value. There are two reasons that might happen:
1. Evaluation of the expression raises an exception.
2. Evaluation of the expression never terminates (e.g., it enters an “infinite loop”).

4.3.1 Primitive Types and Values

The primitive types are the built-in and most basic types: integers, floating-point numbers, characters, strings, and
booleans. They will be recognizable as similar to primitive types from other programming languages.
Type int: Integers. OCaml integers are written as usual: 1, 2, etc. The usual operators are available: +, -, *, /, and
mod. The latter two are integer division and modulus:

65 / 60

30 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

- : int = 1

65 mod 60

- : int = 5

65 / 0

Exception: Division_by_zero.
Raised by primitive operation at unknown location
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

OCaml integers range from −262 to 262 − 1 on modern platforms. They are implemented with 64-bit machine words,
which is the size of a register on 64-bit processor. But one of those bits is “stolen” by the OCaml implementation, leading
to a 63-bit representation. That bit is used at run time to distinguish integers from pointers. For applications that need
true 64-bit integers, there is an Int64 module in the standard library. And for applications that need arbitrary-precision
integers, there is a separate Zarith library. But for most purposes, the built-in int type suffices and offers the best
performance.
Type float: Floating-point numbers. OCaml floats are IEEE 754 double-precision floating-point numbers. Syntac-
tically, they must always contain a dot—for example, 3.14 or 3.0 or even 3.. The last is a float; if you write it as
3, it is instead an int:

3.

- : float = 3.

- : int = 3

OCaml deliberately does not support operator overloading, Arithmetic operations on floats are written with a dot after
them. For example, floating-point multiplication is written *. not *:

3.14 *. 2.

- : float = 6.28

3.14 * 2.

File "[7]", line 1, characters 0-4:


1 | 3.14 * 2.
^^^^
Error: This expression has type float but an expression was expected of type
int

4.3. Expressions 31
OCaml Programming: Correct + Efficient + Beautiful

OCaml will not automatically convert between int and float. If you want to convert, there are two built-in functions
for that purpose: int_of_float and float_of_int.

3.14 *. (float_of_int 2)

- : float = 6.28

As in any language, the floating-point representation is approximate. That can lead to rounding errors:

0.1 +. 0.2

- : float = 0.300000000000000044

The same behavior can be observed in Python and Java, too. If you haven’t encountered this phenomenon before, here’s
a basic guide to floating-point representation that you might enjoy reading.
Type bool: Booleans. The boolean values are written true and false. The usual short-circuit conjunction && and
disjunction || operators are available.
Type char: Characters. Characters are written with single quotes, such as 'a', 'b', and 'c'. They are represented as
bytes —that is, 8-bit integers— in the ISO 8859-1 “Latin-1” encoding. The first half of the characters in that range are the
standard ASCII characters. You can convert characters to and from integers with char_of_int and int_of_char.
Type string: Strings. Strings are sequences of characters. They are written with double quotes, such as "abc". The
string concatenation operator is ^:

"abc" ^ "def"

- : string = "abcdef"

Object-oriented languages often provide an overridable method for converting objects to strings, such as toString()
in Java or __str__() in Python. But most OCaml values are not objects, so another means is required to convert
to strings. For three of the primitive types, there are built-in functions: string_of_int, string_of_float,
string_of_bool. Strangely, there is no string_of_char, but the library function String.make can be used
to accomplish the same goal.

string_of_int 42

- : string = "42"

String.make 1 'z'

- : string = "z"

Likewise, for the same three primitive types, there are built-in functions to convert from a string if possible:
int_of_string, float_of_string, and bool_of_string.

int_of_string "123"

- : int = 123

32 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

int_of_string "not an int"

Exception: Failure "int_of_string".


Raised by primitive operation at unknown location
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

There is no char_of_string, but the individual characters of a string can be accessed by a 0-based index. The
indexing operator is written with a dot and square brackets:

"abc".[0]

- : char = 'a'

"abc".[1]

- : char = 'b'

"abc".[3]

Exception: Invalid_argument "index out of bounds".


Raised by primitive operation at unknown location
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

4.3.2 More Operators

We’ve covered most of the built-in operators above, but there are a few more that you can see in the OCaml manual.
There are two equality operators in OCaml, = and ==, with corresponding inequality operators <> and !=. Operators
= and <> examine structural equality whereas == and != examine physical equality. Until we’ve studied the imperative
features of OCaml, the difference between them will be tricky to explain. See the documentation of Stdlib.(==) if
you’re curious now.

Important: Start training yourself now to use = and not to use ==. This will be difficult if you’re coming from a language
like Java where == is the usual equality operator.

4.3. Expressions 33
OCaml Programming: Correct + Efficient + Beautiful

4.3.3 Assertions

The expression assert e evaluates e. If the result is true, nothing more happens, and the entire expression evaluates
to a special value called unit. The unit value is written () and its type is unit. But if the result is false, an exception
is raised.
One way to test a function f is to write a series of assertions like this:

let () = assert (f input1 = output1)


let () = assert (f input2 = output2)
let () = assert (f input3 = output3)

Those assert that f input1 should be output1, and so forth. The let () = ... part of those is used to handle
the unit value returned by each assertion.

4.3.4 If Expressions

The expression if e1 then e2 else e3 evaluates to e2 if e1 evaluates to true, and to e3 otherwise. We call
e1 the guard of the if expression.

if 3 + 5 > 2 then "yay!" else "boo!"

- : string = "yay!"

Unlike if-then-else statements that you may have used in imperative languages, if-then-else expressions in
OCaml are just like any other expression; they can be put anywhere an expression can go. That makes them similar to
the ternary operator ? : that you might have used in other languages.

4 + (if 'a' = 'b' then 1 else 2)

- : int = 6

If expressions can be nested in a pleasant way:

if e1 then e2
else if e3 then e4
else if e5 then e6
...
else en

You should regard the final else as mandatory, regardless of whether you are writing a single if expression or a highly
nested if expression. If you omit it you’ll likely get an error message that, for now, is inscrutable:

if 2 > 3 then 5

File "[20]", line 1, characters 14-15:


1 | if 2 > 3 then 5
^
Error: This expression has type int but an expression was expected of type
unit
because it is in the result of a conditional with no else branch

Syntax. The syntax of an if expression:

34 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

if e1 then e2 else e3

The letter e is used here to represent any other OCaml expression; it’s an example of a syntactic variable aka metavariable,
which is not actually a variable in the OCaml language itself, but instead a name for a certain syntactic construct. The
numbers after the letter e are being used to distinguish the three different occurrences of it.
Dynamic semantics. The dynamic semantics of an if expression:
• If e1 evaluates to true, and if e2 evaluates to a value v, then if e1 then e2 else e3 evaluates to v
• If e1 evaluates to false, and if e3 evaluates to a value v, then if e1 then e2 else e3 evaluates to v.
We call these evaluation rules: they define how to evaluate expressions. Note how it takes two rules to describe the
evaluation of an if expression, one for when the guard is true, and one for when the guard is false. The letter v is used
here to represent any OCaml value; it’s another example of a metavariable. Later we will develop a more mathematical
way of expressing dynamic semantics, but for now we’ll stick with this more informal style of explanation.
Static semantics. The static semantics of an if expression:
• If e1 has type bool and e2 has type t and e3 has type t then if e1 then e2 else e3 has type t
We call this a typing rule: it describes how to type check an expression. Note how it only takes one rule to describe the
type checking of an if expression. At compile time, when type checking is done, it makes no difference whether the
guard is true or false; in fact, there’s no way for the compiler to know what value the guard will have at run time. The
letter t here is used to represent any OCaml type; the OCaml manual also has definition of all types (which curiously
does not name the base types of the language like int and bool).
We’re going to be writing “has type” a lot, so let’s introduce a more compact notation for it. Whenever we would write
“e has type t”, let’s instead write e : t. The colon is pronounced “has type”. This usage of colon is consistent with
how the toplevel responds after it evaluates an expression that you enter:

let x = 42

val x : int = 42

In the above example, variable x has type int, which is what the colon indicates.

4.3.5 Let Expressions

In our use of the word let thus far, we’ve been making definitions in the toplevel and in .ml files. For example,

let x = 42;;

val x : int = 42

defines x to be 42, after which we can use x in future definitions at the toplevel. We’ll call this use of let a let definition.
There’s another use of let which is as an expression:

let x = 42 in x + 1

- : int = 43

4.3. Expressions 35
OCaml Programming: Correct + Efficient + Beautiful

Here we’re binding a value to the name x then using that binding inside another expression, x+1. We’ll call this use of
let a let expression. Since it’s an expression, it evaluates to a value. That’s different than definitions, which themselves do
not evaluate to any value. You can see that if you try putting a let definition in place of where an expression is expected:

(let x = 42) + 1

File "[24]", line 1, characters 11-12:


1 | (let x = 42) + 1
^
Error: Syntax error

Syntactically, a let definition is not permitted on the left-hand side of the + operator, because a value is needed there,
and definitions do not evaluate to values. On the other hand, a let expression would work fine:

(let x = 42 in x) + 1

- : int = 43

Another way to understand let definitions at the toplevel is that they are like let expression where we just haven’t provided
the body expression yet. Implicitly, that body expression is whatever else we type in the future. For example,

# let a = "big";;
# let b = "red";;
# let c = a ^ b;;
# ...

is understood by OCaml in the same way as

let a = "big" in
let b = "red" in
let c = a ^ b in
...

That latter series of let bindings is idiomatically how several variables can be bound inside a given block of code.
Syntax.

let x = e1 in e2

As usual, x is an identifier. These identifiers must begin with lower-case, not upper, and idiomatically are written with
snake_case not camelCase. We call e1 the binding expression, because it’s what’s being bound to x; and we call
e2 the body expression, because that’s the body of code in which the binding will be in scope.
Dynamic semantics.
To evaluate let x = e1 in e2:
• Evaluate e1 to a value v1.
• Substitute v1 for x in e2, yielding a new expression e2'.
• Evaluate e2' to a value v2.
• The result of evaluating the let expression is v2.
Here’s an example:

36 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

let x = 1 + 4 in x * 3
--> (evaluate e1 to a value v1)
let x = 5 in x * 3
--> (substitute v1 for x in e2, yielding e2')
5 * 3
--> (evaluate e2' to v2)
15
(result of evaluation is v2)

Static semantics.
• If e1 : t1 and if under the assumption that x : t1 it holds that e2 : t2, then (let x = e1 in e2)
: t2.
We use the parentheses above just for clarity. As usual, the compiler’s type inferencer determines what the type of the
variable is, or the programmer could explicitly annotate it with this syntax:

let x : t = e1 in e2

4.3.6 Scope

Let bindings are in effect only in the block of code in which they occur. This is exactly what you’re used to from nearly
any modern programming language. For example:

let x = 42 in
(* y is not meaningful here *)
x + (let y = "3110" in
(* y is meaningful here *)
int_of_string y)

The scope of a variable is where its name is meaningful. Variable y is in scope only inside of the let expression that
binds it above.
It’s possible to have overlapping bindings of the same name. For example:

let x = 5 in
((let x = 6 in x) + x)

But this is darn confusing, and for that reason, it is strongly discouraged style—much like ambiguous pronouns are
discouraged in natural language. Nonetheless, let’s consider what that code means.
To what value does that code evaluate? The answer comes down to how x is replaced by a value each time it occurs. Here
are a few possibilities for such substitution:

(* possibility 1 *)
let x = 5 in
((let x = 6 in 6) + 5)

(* possibility 2 *)
let x = 5 in
((let x = 6 in 5) + 5)

(* possibility 3 *)
let x = 5 in
((let x = 6 in 6) + 6)

4.3. Expressions 37
OCaml Programming: Correct + Efficient + Beautiful

The first one is what nearly any reasonable language would do. And most likely it’s what you would guess But, why?
The answer is something we’ll call the Principle of Name Irrelevance: the name of a variable shouldn’t intrinsically matter.
You’re used to this from math. For example, the following two functions are the same:

𝑓(𝑥) = 𝑥2
𝑓(𝑦) = 𝑦2

It doesn’t intrinsically matter whether we call the argument to the function 𝑥 or 𝑦; either way, it’s still the squaring function.
Therefore, in programs, these two functions should be identical:

let f x = x * x
let f y = y * y

This principle is more commonly known as alpha equivalence: the two functions are equivalent up to renaming of variables,
which is also called alpha conversion for historical reasons that are unimportant here.
According to the Principle of Name Irrelevance, these two expressions should be identical:

let x = 6 in x
let y = 6 in y

Therefore, the following two expressions, which have the above expressions embedded in them, should also be identical:

let x = 5 in (let x = 6 in x) + x
let x = 5 in (let y = 6 in y) + x

But for those to be identical, we must choose the first of the three possibilities above. It is the only one that makes the
name of the variable be irrelevant.
There is a term commonly used for this phenomenon: a new binding of a variable shadows any old binding of the variable
name. Metaphorically, it’s as if the new binding temporarily casts a shadow over the old binding. But eventually the old
binding could reappear as the shadow recedes.
Shadowing is not mutable assignment. For example, both of the following expressions evaluate to 11:

let x = 5 in ((let x = 6 in x) + x)
let x = 5 in (x + (let x = 6 in x))

Likewise, the following utop transcript is not mutable assignment, though at first it could seem like it is:

# let x = 42;;
val x : int = 42
# let x = 22;;
val x : int = 22

Recall that every let definition in the toplevel is effectively a nested let expression. So the above is effectively the
following:

let x = 42 in
let x = 22 in
... (* whatever else is typed in the toplevel *)

The right way to think about this is that the second let binds an entirely new variable that just happens to have the same
name as the first let.
Here is another utop transcript that is well worth studying:

38 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

# let x = 42;;
val x : int = 42
# let f y = x + y;;
val f : int -> int = <fun>
# f 0;;
: int = 42
# let x = 22;;
val x : int = 22
# f 0;;
- : int = 42 (* x did not mutate! *)

To summarize, each let definition binds an entirely new variable. If that new variable happens to have the same name as
an old variable, the new variable temporarily shadows the old one. But the old variable is still around, and its value is
immutable: it never, ever changes. So even though let expressions might superficially look like assignment statements
from imperative languages, they are actually quite different.

4.3.7 Type Annotations

OCaml automatically infers the type of every expression, with no need for the programmer to write it manually. Nonethe-
less, it can sometimes be useful to manually specify the desired type of an expression. A type annotation does that:

(5 : int)

- : int = 5

An incorrect annotation will produce a compile-time error:

(5 : float)

File "[27]", line 1, characters 1-2:


1 | (5 : float)
^
Error: This expression has type int but an expression was expected of type
float
Hint: Did you mean `5.'?

And that example shows why you might use manual type annotations during debugging. Perhaps you had forgotten that
5 cannot be treated as a float, and you tried to write:

5 +. 1.1

You might try manually specifying that 5 was supposed to be a float:

(5 : float) +. 1.1

File "[28]", line 1, characters 1-2:


1 | (5 : float) +. 1.1
^
Error: This expression has type int but an expression was expected of type
float
Hint: Did you mean `5.'?

4.3. Expressions 39
OCaml Programming: Correct + Efficient + Beautiful

It’s clear that the type annotation has failed. Although that might seem silly for this tiny program, you might find this
technique to be effective as programs get larger.

Important: Type annotations are not type casts, such as might be found in C or Java. They do not indicate a conversion
from one type to another. Rather they indicate a check that the expression really does have the given type.

Syntax. The syntax of a type annotation:

(e : t)

Note that the parentheses are required.


Dynamic semantics. There is no run-time meaning for a type annotation. It goes away during compilation, because
it indicates a compile-time check. There is no run-time conversion. So, if (e : t) compiled successfully, then at
run-time it is simply e, and it evaluates as e would.
Static semantics. If e has type t then (e : t) has type t.

4.4 Functions

Since OCaml is a functional language, there’s a lot to cover about functions. Let’s get started.

Important: Methods and functions are not the same idea. A method is a component of an object, and it implicitly has
a receiver that is usually accessed with a keyword like this or self. OCaml functions are not methods: they are not
components of objects, and they do not have a receiver.
Some might say that all methods are functions, but not all functions are methods. Some might even quibble with that,
making a distinction between functions and procedures. The latter would be functions that do not return any meaningful
value, such as a void return type in Java or None return value in Python.
So if you’re coming from an object-oriented background, be careful about the terminology. Everything here is strictly
a function, not a method.

4.4.1 Function Definitions

The following code

let x = 42

has an expression in it (42) but is not itself an expression. Rather, it is a definition. Definitions bind values to names, in
this case the value 42 being bound to the name x. The OCaml manual describes definitions (see the third major grouping
titled “definition” on that page), but that manual page is again primarily for reference not for study. Definitions are not
expressions, nor are expressions definitions—they are distinct syntactic classes.
For now, let’s focus on one particular kind of definition, a function definition. Non-recursive functions are defined like
this:

let f x = ...

Recursive functions are defined like this:

40 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

let rec f x = ...

The difference is just the rec keyword. It’s probably a bit surprising that you explicitly have to add a keyword to make a
function recursive, because most languages assume by default that they are. OCaml doesn’t make that assumption, though.
(Nor does the Scheme family of languages.)
One of the best known recursive functions is the factorial function. In OCaml, it can be written as follows:

(** [fact n] is [n!].


Requires: [n >= 0]. *)
let rec fact n = if n = 0 then 1 else n * fact (n - 1)

val fact : int -> int = <fun>

We provided a specification comment above the function to document the precondition (Requires) and postcondition
(is) of the function.
Note that, as in many languages, OCaml integers are not the “mathematical” integers but are limited to a fixed number
of bits. The manual specifies that (signed) integers are at least 31 bits, but they could be wider. As architectures have
grown, so has that size. In current implementations, OCaml integers are 63 bits. So if you test on large enough inputs,
you might begin to see strange results. The problem is machine arithmetic, not OCaml. (For interested readers: why 31
or 63 instead of 32 or 64? The OCaml garbage collector needs to distinguish between integers and pointers. The runtime
representation of these therefore steals one bit to flag whether a word is an integer or a pointer.)
Here’s another recursive function:

(** [pow x y] is [x] to the power of [y].


Requires: [y >= 0]. *)
let rec pow x y = if y = 0 then 1 else x * pow x (y - 1)

val pow : int -> int -> int = <fun>

Note how we didn’t have to write any types in either of our functions: the OCaml compiler infers them for us automatically.
The compiler solves this type inference problem algorithmically, but we could do it ourselves, too. It’s like a mystery that
can be solved by our mental power of deduction:
• Since the if expression can return 1 in the then branch, we know by the typing rule for if that the entire if
expression has type int.
• Since the if expression has type int, the function’s return type must be int.
• Since y is compared to 0 with the equality operator, y must be an int.
• Since x is multiplied with another expression using the * operator, x must be an int.
If we wanted to write down the types for some reason, we could do that:

let rec pow (x : int) (y : int) : int = ...

The parentheses are mandatory when we write the type annotations for x and y. We will generally leave out these
annotations, because it’s simpler to let the compiler infer them. There are other times when you’ll want to explicitly
write down types. One particularly useful time is when you get a type error from the compiler that you don’t understand.
Explicitly annotating the types can help with debugging such an error message.
Syntax. The syntax for function definitions:

4.4. Functions 41
OCaml Programming: Correct + Efficient + Beautiful

let rec f x1 x2 ... xn = e

The f is a metavariable indicating an identifier being used as a function name. These identifiers must begin with a
lowercase letter. The remaining rules for lowercase identifiers can be found in the manual. The names x1 through xn
are metavariables indicating argument identifiers. These follow the same rules as function identifiers. The keyword rec
is required if f is to be a recursive function; otherwise it may be omitted.
Note that syntax for function definitions is actually simplified compared to what OCaml really allows. We will learn more
about some augmented syntax for function definition in the next couple of weeks. But for now, this simplified version will
help us focus.
Mutually recursive functions can be defined with the and keyword:

let rec f x1 ... xn = e1


and g y1 ... yn = e2

For example:

(** [even n] is whether [n] is even.


Requires: [n >= 0]. *)
let rec even n =
n = 0 || odd (n - 1)

(** [odd n] is whether [n] is odd.


Requires: [n >= 0]. *)
and odd n =
n <> 0 && even (n - 1);;

val even : int -> bool = <fun>


val odd : int -> bool = <fun>

The syntax for function types is:

t -> u
t1 -> t2 -> u
t1 -> ... -> tn -> u

The t and u are metavariables indicating types. Type t -> u is the type of a function that takes an input of type t and
returns an output of type u. We can think of t1 -> t2 -> u as the type of a function that takes two inputs, the first
of type t1 and the second of type t2, and returns an output of type u. Likewise for a function that takes n arguments.
Dynamic semantics. There is no dynamic semantics of function definitions. There is nothing to be evaluated. OCaml
just records that the name f is bound to a function with the given arguments x1..xn and the given body e. Only later,
when the function is applied, will there be some evaluation to do.
Static semantics. The static semantics of function definitions:
• For non-recursive functions: if by assuming that x1 : t1 and x2 : t2 and … and xn : tn, we can
conclude that e : u, then f : t1 -> t2 -> ... -> tn -> u.
• For recursive functions: if by assuming that x1 : t1 and x2 : t2 and … and xn : tn and f : t1 ->
t2 -> ... -> tn -> u, we can conclude that e : u, then f : t1 -> t2 -> ... -> tn -> u.
Note how the type checking rule for recursive functions assumes that the function identifier f has a particular type, then
checks to see whether the body of the function is well-typed under that assumption. This is because f is in scope inside
the function body itself (just like the arguments are in scope).

42 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

4.4.2 Anonymous Functions

We already know that we can have values that are not bound to names. The integer 42, for example, can be entered at
the toplevel without giving it a name:

42

- : int = 42

Or we can bind it to a name:

let x = 42

val x : int = 42

Similarly, OCaml functions do not have to have names; they may be anonymous. For example, here is an anonymous
function that increments its input: fun x -> x + 1. Here, fun is a keyword indicating an anonymous function, x is
the argument, and -> separates the argument from the body.
We now have two ways we could write an increment function:

let inc x = x + 1
let inc = fun x -> x + 1

val inc : int -> int = <fun>

val inc : int -> int = <fun>

They are syntactically different but semantically equivalent. That is, even though they involve different keywords and put
some identifiers in different places, they mean the same thing.
Anonymous functions are also called lambda expressions, a term that comes from the lambda calculus, which is a math-
ematical model of computation in the same sense that Turing machines are a model of computation. In the lambda
calculus, fun x -> e would be written 𝜆𝑥.𝑒. The 𝜆 denotes an anonymous function.
It might seem a little mysterious right now why we would want functions that have no names. Don’t worry; we’ll see good
uses for them later in the course, especially when we study so-called “higher-order programming”. In particular, we will
often create anonymous functions and pass them as input to other functions.
Syntax.

fun x1 ... xn -> e

Static semantics.
• If by assuming that x1 : t1 and x2 : t2 and … and xn : tn, we can conclude that e : u, then fun
x1 ... xn -> e : t1 -> t2 -> ... -> tn -> u.
Dynamic semantics. An anonymous function is already a value. There is no computation to be performed.

4.4. Functions 43
OCaml Programming: Correct + Efficient + Beautiful

4.4.3 Function Application

Here we cover a somewhat simplified syntax of function application compared to what OCaml actually allows.
Syntax.

e0 e1 e2 ... en

The first expression e0 is the function, and it is applied to arguments e1 through en. Note that parentheses are not
required around the arguments to indicate function application, as they are in languages in the C family, including Java.
Static semantics.
• If e0 : t1 -> ... -> tn -> u and e1 : t1 and … and en : tn then e0 e1 ... en : u.
Dynamic semantics.
To evaluate e0 e1 ... en:
1. Evaluate e0 to a function. Also evaluate the argument expressions e1 through en to values v1 through vn.
For e0, the result might be an anonymous function fun x1 ... xn -> e or a name f. In the latter case, we
need to find the definition of f, which we can assume to be of the form let rec f x1 ... xn = e. Either
way, we now know the argument names x1 through xn and the body e.
2. Substitute each value vi for the corresponding argument name xi in the body e of the function. That substitution
results in a new expression e'.
3. Evaluate e' to a value v, which is the result of evaluating e0 e1 ... en.
If you compare these evaluation rules to the rules for let expressions, you will notice they both involve substitution. This
is not an accident. In fact, anywhere let x = e1 in e2 appears in a program, we could replace it with (fun x
-> e2) e1. They are syntactically different but semantically equivalent. In essence, let expressions are just syntactic
sugar for anonymous function application.

4.4.4 Pipeline

There is a built-in infix operator in OCaml for function application called the pipeline operator, written |>. Imagine that
as depicting a triangle pointing to the right. The metaphor is that values are sent through the pipeline from left to right.
For example, suppose we have the increment function inc from above as well as a function square that squares its
input:

let square x = x * x

val square : int -> int = <fun>

Here are two equivalent ways of squaring 6:

square (inc 5);;


5 |> inc |> square;;

- : int = 36

- : int = 36

44 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

The latter uses the pipeline operator to send 5 through the inc function, then send the result of that through the square
function. This is a nice, idiomatic way of expressing the computation in OCaml. The former way is arguably not as
elegant: it involves writing extra parentheses and requires the reader’s eyes to jump around, rather than move linearly
from left to right. The latter way scales up nicely when the number of functions being applied grows, whereas the former
way requires more and more parentheses:

5 |> inc |> square |> inc |> inc |> square;;
square (inc (inc (square (inc 5))));;

- : int = 1444

- : int = 1444

It might feel weird at first, but try using the pipeline operator in your own code the next time you find yourself writing a
big chain of function applications.
Since e1 |> e2 is just another way of writing e2 e1, we don’t need to state the semantics for |>: it’s just the
same as function application. These two programs are another example of expressions that are syntactically different but
semantically equivalent.

4.4.5 Polymorphic Functions

The identity function is the function that simply returns its input:

let id x = x

val id : 'a -> 'a = <fun>

Or equivalently as an anonymous function:

let id = fun x -> x

val id : 'a -> 'a = <fun>

The 'a is a type variable: it stands for an unknown type, just like a regular variable stands for an unknown value.
Type variables always begin with a single quote. Commonly used type variables include 'a, 'b, and 'c, which OCaml
programmers typically pronounce in Greek: alpha, beta, and gamma.
We can apply the identity function to any type of value we like:

id 42;;
id true;;
id "bigred";;

- : int = 42

- : bool = true

- : string = "bigred"

4.4. Functions 45
OCaml Programming: Correct + Efficient + Beautiful

Because you can apply id to many types of values, it is a polymorphic function: it can be applied to many (poly) forms
(morph).
With manual type annotations, it’s possible to give a more restrictive type to a polymorphic function than the type the
compiler automatically infers. For example:

let id_int (x : int) : int = x

val id_int : int -> int = <fun>

That’s the same function as id, except for the two manual type annotations. Because of those, we cannot apply id_int
to a bool like we did id:

id_int true

File "[14]", line 1, characters 7-11:


1 | id_int true
^^^^
Error: This expression has type bool but an expression was expected of type
int

Another way of writing id_int would be in terms of id:

let id_int' : int -> int = id

val id_int' : int -> int = <fun>

In effect, we took a value of type 'a -> 'a, and we bound it to a name whose type was manually specified as being
int -> int. You might ask, why does that work? They aren’t the same types, after all.
One way to think about this is in terms of behavior. The type of id_int specifies one aspect of its behavior: given an
int as input, it promises to produce an int as output. It turns out that id also makes the same promise: given an int
as input, it too will return an int as output. Now id also makes many more promises, such as: given a bool as input,
it will return a bool as output. So by binding id to a more restrictive type int -> int, we have thrown away all
those additional promises as irrelevant. Sure, that’s information lost, but at least no promises will be broken. It’s always
going to be safe to use a function of type 'a -> 'a when what we needed was a function of type int -> int.
The converse is not true. If we needed a function of type 'a -> 'a but tried to use a function of type int -> int,
we’d be in trouble as soon as someone passed an input of another type, such as bool. To prevent that trouble, OCaml
does something potentially surprising with the following code:

let id' : 'a -> 'a = fun x -> x + 1

val id' : int -> int = <fun>

Function id' is actually the increment function, not the identity function. So passing it a bool or string or some
complicated data structure is not safe; the only data + can safely manipulate are integers. OCaml therefore instantiates
the type variable 'a to int, thus preventing us from applying id' to non-integers:

id' true

46 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

File "[17]", line 1, characters 4-8:


1 | id' true
^^^^
Error: This expression has type bool but an expression was expected of type
int

That leads us to another, more mechanical, way to think about all of this in terms of application. By that we mean the
very same notion of how a function is applied to arguments: when evaluating the application id 5, the argument x is
instantiated with value 5. Likewise, the 'a in the type of id is being instantiated with type int at that application. So
if we write

let id_int' : int -> int = id

val id_int' : int -> int = <fun>

we are in fact instantiating the 'a in the type of id with the type int. And just as there is no way to “unapply” a
function—for example, given 5 we can’t compute backwards to id 5—we can’t unapply that type instantiation and
change int back to 'a.
To make that precise, suppose we have a let definition [or expression]:

let x = e [in e']

and that OCaml infers x has a type t that includes some type variables 'a, 'b, etc. Then we are permitted to instantiate
those type variables. We can do that by applying the function to arguments that reveal what the type instantiations should
be (as in id 5) or by a type annotation (as in id_int'), among other ways. But we have to be consistent with the
instantiation. For example, we cannot take a function of type 'a -> 'b -> 'a and instantiate it at type int ->
'b -> string, because the instantiation of 'a is not the same type in each of the two places it occurred:

let first x y = x;;


let first_int : int -> 'b -> int = first;;
let bad_first : int -> 'b -> string = first;;

val first : 'a -> 'b -> 'a = <fun>

val first_int : int -> 'b -> int = <fun>

File "[19]", line 3, characters 38-43:


3 | let bad_first : int -> 'b -> string = first;;
^^^^^
Error: This expression has type int -> 'b -> int
but an expression was expected of type int -> 'b -> string
Type int is not compatible with type string

4.4. Functions 47
OCaml Programming: Correct + Efficient + Beautiful

4.4.6 Labeled and Optional Arguments

The type and name of a function usually give you a pretty good idea of what the arguments should be. However, for
functions with many arguments (especially arguments of the same type), it can be useful to label them. For example, you
might guess that the function String.sub returns a substring of the given string (and you would be correct). You could
type in String.sub to find its type:

String.sub;;

- : string -> int -> int -> string = <fun>

But it’s not clear from the type how to use it—you’re forced to consult the documentation.
OCaml supports labeled arguments to functions. You can declare this kind of function using the following syntax:

let f ~name1:arg1 ~name2:arg2 = arg1 + arg2;;

val f : name1:int -> name2:int -> int = <fun>

This function can be called by passing the labeled arguments in either order:

f ~name2:3 ~name1:4

Labels for arguments are often the same as the variable names for them. OCaml provides a shorthand for this case. The
following are equivalent:

let f ~name1:name1 ~name2:name2 = name1 + name2


let f ~name1 ~name2 = name1 + name2

Use of labeled arguments is largely a matter of taste. They convey extra information, but they can also add clutter to
types.
The syntax to write both a labeled argument and an explicit type annotation for it is:

let f ~name1:(arg1 : int) ~name2:(arg2 : int) = arg1 + arg2

It is also possible to make some arguments optional. When called without an optional argument, a default value will be
provided. To declare such a function, use the following syntax:

let f ?name:(arg1=8) arg2 = arg1 + arg2

val f : ?name:int -> int -> int = <fun>

You can then call a function with or without the argument:

f ~name:2 7

- : int = 9

f 7

48 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

- : int = 15

4.4.7 Partial Application

We could define an addition function as follows:

let add x y = x + y

val add : int -> int -> int = <fun>

Here’s a rather similar function:

let addx x = fun y -> x + y

val addx : int -> int -> int = <fun>

Function addx takes an integer x as input and returns a function of type int -> int that will add x to whatever is
passed to it.
The type of addx is int -> int -> int. The type of add is also int -> int -> int. So from the perspective
of their types, they are the same function. But the form of addx suggests something interesting: we can apply it to just
a single argument.

let add5 = addx 5

val add5 : int -> int = <fun>

add5 2

- : int = 7

It turns out the same can be done with add:

let add5 = add 5

val add5 : int -> int = <fun>

add5 2;;

- : int = 7

What we just did is called partial application: we partially applied the function add to one argument, even though you
would normally think of it as a multi-argument function. This works because the following three functions are syntactically
different but semantically equivalent. That is, they are different ways of expressing the same computation:

let add x y = x + y
let add x = fun y -> x + y
let add = fun x -> (fun y -> x + y)

4.4. Functions 49
OCaml Programming: Correct + Efficient + Beautiful

So add is really a function that takes an argument x and returns a function (fun y -> x + y). Which leads us to a
deep truth…

4.4.8 Function Associativity

Are you ready for the truth? Take a deep breath. Here goes…
Every OCaml function takes exactly one argument.
Why? Consider add: although we can write it as let add x y = x + y, we know that’s semantically equivalent
to let add = fun x -> (fun y -> x + y). And in general,

let f x1 x2 ... xn = e

is semantically equivalent to

let f =
fun x1 ->
(fun x2 ->
(...
(fun xn -> e)...))

So even though you think of f as a function that takes n arguments, in reality it is a function that takes 1 argument and
returns a function.
The type of such a function

t1 -> t2 -> t3 -> t4

really means the same as

t1 -> (t2 -> (t3 -> t4))

That is, function types are right associative: there are implicit parentheses around function types, from right to left. The
intuition here is that a function takes a single argument and returns a new function that expects the remaining arguments.
Function application, on the other hand, is left associative: there are implicit parentheses around function applications,
from left to right. So

e1 e2 e3 e4

really means the same as

((e1 e2) e3) e4

The intuition here is that the left-most expression grabs the next expression to its right as its single argument.

50 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

4.4.9 Operators as Functions

The addition operator + has type int -> int -> int. It is normally written infix, e.g., 3 + 4. By putting
parentheses around it, we can make it a prefix operator:

( + )

- : int -> int -> int = <fun>

( + ) 3 4;;

- : int = 7

let add3 = ( + ) 3

val add3 : int -> int = <fun>

add3 2

- : int = 5

The same technique works for any built-in operator.


Normally the spaces are unnecessary. We could write (+) or ( + ), but it is best to include them. Beware of
multiplication, which must be written as ( * ), because (*) would be parsed as beginning a comment.
We can even define our own new infix operators, for example:

let ( ^^ ) x y = max x y

And now 2 ^^ 3 evaluates to 3.


The rules for which punctuation can be used to create infix operators are not necessarily intuitive. Nor is the relative
precedence with which such operators will be parsed. So be careful with this usage.

4.4.10 Tail Recursion

Consider the following seemingly uninteresting function, which counts from 1 to n:

(** [count n] is [n], computed by adding 1 to itself [n] times. That is,
this function counts up from 1 to [n]. *)
let rec count n =
if n = 0 then 0 else 1 + count (n - 1)

val count : int -> int = <fun>

Counting to 10 is no problem:

count 10

4.4. Functions 51
OCaml Programming: Correct + Efficient + Beautiful

- : int = 10

Counting to 100,000 is no problem either:

count 100_000

- : int = 100000

But try counting to 1,000,000 and you’ll get the following error:

Stack overflow during evaluation (looping recursion?).

What’s going on here?


The Call Stack. The issue is that the call stack has a limited size. You probably learned in one of your introductory
programming classes that most languages implement function calls with a stack. That stack contains one element for each
function call that has been started but has not yet completed. Each element stores information like the values of local
variables and which instruction in the function is currently being executed. When the evaluation of one function body
calls another function, a new element is pushed on the call stack, and it is popped off when the called function completes.
The size of the stack is usually limited by the operating system. So if the stack runs out of space, it becomes impossible
to make another function call. Normally this doesn’t happen, because there’s no reason to make that many successive
function calls before returning. In cases where it does happen, there’s good reason for the operating system to make that
program stop: it might be in the process of eating up all the memory available on the entire computer, thus harming other
programs running on the same computer. The count function isn’t likely to do that, but this function would:

let rec count_forever n = 1 + count_forever n

val count_forever : 'a -> int = <fun>

So the operating system for safety’s sake limits the call stack size. That means eventually count will run out of stack
space on a large enough input. Notice how that choice is really independent of the programming language. So this same
issue can and does occur in languages other than OCaml, including Python and Java. You’re just less likely to have seen
it manifest there, because you probably never wrote quite as many recursive functions in those languages.
Tail Recursion. There is a solution to this issue that was described in a 1977 paper about LISP by Guy Steele. The
solution, tail-call optimization, requires some cooperation between the programmer and the compiler. The programmer
does a little rewriting of the function, which the compiler then notices and applies an optimization. Let’s see how it works.
Suppose that a recursive function f calls itself then returns the result of that recursive call. Our count function does not
do that:

let rec count n =


if n = 0 then 0 else 1 + count (n - 1)

val count : int -> int = <fun>

Rather, after the recursive call count (n - 1), there is computation remaining: the computer still needs to add 1 to
the result of that call.
But we as programmers could rewrite the count function so that it does not need to do any additional computation after
the recursive call. The trick is to create a helper function with an extra parameter:

52 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

let rec count_aux n acc =


if n = 0 then acc else count_aux (n - 1) (acc + 1)

let count_tr n = count_aux n 0

val count_aux : int -> int -> int = <fun>

val count_tr : int -> int = <fun>

Function count_aux is almost the same as our original count, but it adds an extra parameter named acc, which is
idiomatic and stands for “accumulator”. The idea is that the value we want to return from the function is slowly, with
each recursive call, being accumulated in it. The “remaining computation” —the addition of 1— now happens before
the recursive call not after. When the base case of the recursion finally arrives, the function now returns acc, where the
answer has been accumulated.
But the original base case of 0 still needs to exist in the code somewhere. And it does, as the original value of acc that
is passed to count_aux. Now count_tr (we’ll get to why the name is “tr” in just a minute) works as a replacement
for our original count.
At this point we’ve completed the programmer’s responsibility, but it’s probably not clear why we went through this effort.
After all count_aux will still call itself recursively too many times as count did, and eventually overflow the stack.
That’s where the compiler’s responsibility kicks in. A good compiler (and the OCaml compiler is good this way) can
notice when a recursive call is in tail position, which is a technical way of saying “there’s no more computation to be done
after it returns”. The recursive call to count_aux is in tail position; the recursive call to count is not. Here they are
again so you can compare them:

let rec count n =


if n = 0 then 0 else 1 + count (n - 1)

let rec count_aux n acc =


if n = 0 then acc else count_aux (n - 1) (acc + 1)

Here’s why tail position matters: A recursive call in tail position does not need a new stack frame. It can just reuse
the existing stack frame. That’s because there’s nothing left of use in the existing stack frame! There’s no computation
left to be done, so none of the local variables, or next instruction to execute, etc. matter any more. None of that memory
ever needs to be read again, because that call is effectively already finished. So instead of wasting space by allocating
another stack frame, the compiler “recycles” the space used by the previous frame.
This is the tail-call optimization. It can even be applied in cases beyond recursive functions if the calling function’s
stack frame is suitably compatible with the callee. And, it’s a big deal. The tail-call optimization reduces the stack
space requirements from linear to constant. Whereas count needed 𝑂(𝑛) stack frames, count_aux needs only 𝑂(1),
because the same frame gets reused over and over again for each recursive call. And that means count_tr actually can
count to 1,000,000:

count_tr 1_000_000

- : int = 1000000

Finally, why did we name this function count_tr? The “tr” stands for tail recursive. A tail recursive function is a
recursive function whose recursive calls are all in tail position. In other words, it’s a function that (unless there are other
pathologies) will not exhaust the stack.
The Importance of Tail Recursion. Sometimes beginning functional programmers fixate a bit too much upon it. If all
you care about is writing the first draft of a function, you probably don’t need to worry about tail recursion. It’s pretty easy

4.4. Functions 53
OCaml Programming: Correct + Efficient + Beautiful

to make it tail recursive later if you need to, just by adding an accumulator argument. Or maybe you should rethink how
you have designed the function. Take count, for example: it’s kind of dumb. But later we’ll see examples that aren’t
dumb, such as iterating over lists with thousands of elements.
It is important that the compiler support the optimization. Otherwise, the transformation you do to the code as a pro-
grammer makes no difference. Indeed, most compilers do support it, at least as an option. Java is a notable exception.
The Recipe for Tail Recursion. In a nutshell, here’s how we made a function be tail recursive:
1. Change the function into a helper function. Add an extra argument: the accumulator, often named acc.
2. Write a new “main” version of the function that calls the helper. It passes the original base case’s return value as
the initial value of the accumulator.
3. Change the helper function to return the accumulator in the base case.
4. Change the helper function’s recursive case. It now needs to do the extra work on the accumulator argument, before
the recursive call. This is the only step that requires much ingenuity.
An Example: Factorial. Let’s transform this factorial function to be tail recursive:

(** [fact n] is [n] factorial. *)


let rec fact n =
if n = 0 then 1 else n * fact (n - 1)

val fact : int -> int = <fun>

First, we change its name and add an accumulator argument:

let rec fact_aux n acc = ...

Second, we write a new “main” function that calls the helper with the original base case as the accumulator:

let rec fact_tr n = fact_aux n 1

Third, we change the helper function to return the accumulator in the base case:

if n = 0 then acc ...

Finally, we change the recursive case:

else fact_aux (n - 1) (n * acc)

Putting it all together, we have:

let rec fact_aux n acc =


if n = 0 then acc else fact_aux (n - 1) (n * acc)

let fact_tr n = fact_aux n 1

val fact_aux : int -> int -> int = <fun>

val fact_tr : int -> int = <fun>

It was a nice exercise, but maybe not worthwhile. Even before we exhaust the stack space, the computation suffers from
integer overflow:

54 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

fact 50

- : int = -3258495067890909184

To solve that problem, we turn to OCaml’s big integer library, Zarith. Here we use a few OCaml features that are beyond
anything we’ve seen so far, but hopefully nothing terribly surprising. (If you want to follow along with this code, first
install Zarith in OPAM with opam install zarith.)

#require "zarith.top";;

let rec zfact_aux n acc =


if Z.equal n Z.zero then acc else zfact_aux (Z.pred n) (Z.mul acc n);;

let zfact_tr n = zfact_aux n Z.one;;

zfact_tr (Z.of_int 50)

val zfact_aux : Z.t -> Z.t -> Z.t = <fun>

val zfact_tr : Z.t -> Z.t = <fun>

- : Z.t = 30414093201713378043612608166064768844377641568960512000000000000

If you want you can use that code to compute zfact_tr 1_000_000 without stack or integer overflow, though it will
take several minutes.
The chapter on modules will explain the OCaml features we used above in detail, but for now:
• #require loads the library, which provides a module named Z. Recall that ℤ is the symbol used in mathematics
to denote the integers.
• Z.n means the name n defined inside of Z.
• The type Z.t is the library’s name for the type of big integers.
• We use library values Z.equal for equality comparison, Z.zero for 0, Z.pred for predecessor (i.e., subtract-
ing 1), Z.mul for multiplication, Z.one for 1, and Z.of_int to convert a primitive integer to a big integer.

4.5 Documentation

OCaml provides a tool called OCamldoc that works a lot like Java’s Javadoc tool: it extracts specially formatted comments
from source code and renders them as HTML, making it easy for programmers to read documentation.

4.5. Documentation 55
OCaml Programming: Correct + Efficient + Beautiful

4.5.1 How to Document

Here’s an example of an OCamldoc comment:

(** [sum lst] is the sum of the elements of [lst]. *)


let rec sum lst = ...

• The double asterisk is what causes the comment to be recognized as an OCamldoc comment.
• The square brackets around parts of the comment mean that those parts should be rendered in HTML as type-
writer font rather than the regular font.
Also like Javadoc, OCamldoc supports documentation tags, such as @author, @deprecated, @param, @return,
etc. For example, in the first line of most programming assignments, we ask you to complete a comment like this:

(** @author Your Name (your netid) *)

For the full range of possible markup inside a OCamldoc comment, see the OCamldoc manual. But what we’ve covered
here is good enough for most documentation that you’ll need to write.

4.5.2 What to Document

The documentation style we favor in this book resembles that of the OCaml standard library: concise and declarative. As
an example, let’s revisit the documentation of sum:

(** [sum lst] is the sum of the elements of [lst]. *)


let rec sum lst = ...

That comment starts with sum lst, which is an example application of the function to an argument. The comment
continues with the word “is”, thus declaratively describing the result of the application. (The word “returns” could be used
instead, but “is” emphasizes the mathematical nature of the function.) That description uses the name of the argument,
lst, to explain the result.
Note how there is no need to add tags to redundantly describe parameters or return values, as is often done with Javadoc.
Everything that needs to be said has already been said. We strongly discourage documentation like the following:

(** Sum a list.


@param lst The list to be summed.
@return The sum of the list. *)
let rec sum lst = ...

That poor documentation takes three needlessly hard-to-read lines to say the same thing as the limpid one-line version.
There is one way we might improve the documentation we have so far, which is to explicitly state what happens with
empty lists:

(** [sum lst] is the sum of the elements of [lst].


The sum of an empty list is 0. *)
let rec sum lst = ...

56 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

4.5.3 Preconditions and Postconditions

Here are a few more examples of comments written in the style we favor.

(** [lowercase_ascii c] is the lowercase ASCII equivalent of


character [c]. *)

(** [index s c] is the index of the first occurrence of


character [c] in string [s]. Raises: [Not_found]
if [c] does not occur in [s]. *)

(** [random_int bound] is a random integer between 0 (inclusive)


and [bound] (exclusive). Requires: [bound] is greater than 0
and less than 2^30. *)

The documentation of index specifies that the function raises an exception, as well as what that exception is and the
condition under which it is raised. (We will cover exceptions in more detail in the next chapter.) The documentation of
random_int specifies that the function’s argument must satisfy a condition.
In previous courses, you were exposed to the ideas of preconditions and postconditions. A precondition is something that
must be true before some section of code; and a postcondition, after.
The “Requires” clause above in the documentation of random_int is a kind of precondition. It says that the client
of the random_int function is responsible for guaranteeing something about the value of bound. Likewise, the first
sentence of that same documentation is a kind of postcondition. It guarantees something about the value returned by the
function.
The “Raises” clause in the documentation of index is another kind of postcondition. It guarantees that the function
raises an exception. Note that the clause is not a precondition, even though it states a condition in terms of an input.
Note that none of these examples has a “Requires” clause that says something about the type of an input. If you’re com-
ing from a dynamically-typed language, like Python, this could be a surprise. Python programmers frequently document
preconditions regarding the types of function inputs. OCaml programmers, however, do not. That’s because the compiler
itself does the type checking to ensure that you never pass a value of the wrong type to a function. Consider lower-
case_ascii again: although the English comment helpfully identifies the type of c to the reader, the comment does
not state a “Requires” clause like this:

(** [lowercase_ascii c] is the lowercase ASCII equivalent of [c].


Requires: [c] is a character. *)

Such a comment reads as highly unidiomatic to an OCaml programmer, who would read that comment and be puzzled,
perhaps thinking: “Well of course c is a character; the compiler will guarantee that. What did the person who wrote that
really mean? Is there something they or I am missing?”

4.6 Printing

OCaml has built-in printing functions for a few of the built-in primitive types: print_char, print_string,
print_int, and print_float. There’s also a print_endline function, which is like print_string, but
also outputs a newline.

print_endline "Camels are bae"

Camels are bae

4.6. Printing 57
OCaml Programming: Correct + Efficient + Beautiful

- : unit = ()

4.6.1 Unit

Let’s look at the types of a couple of those functions:

print_endline

- : string -> unit = <fun>

print_string

- : string -> unit = <fun>

They both take a string as input and return a value of type unit, which we haven’t seen before. There is only one value
of this type, which is written () and is also pronounced “unit”. So unit is like bool, except there is one fewer value
of type unit than there is of bool.
Unit is used when you need to take an argument or return a value, but there’s no interesting value to pass or return. It is
the equivalent of void in Java, and is similar to None in Python. Unit is often used when you’re writing or using code
that has side effects. Printing is an example of a side effect: it changes the world and can’t be undone.

4.6.2 Semicolon

If you want to print one thing after another, you could sequence some print functions using nested let expressions:

let _ = print_endline "Camels" in


let _ = print_endline "are" in
print_endline "bae"

Camels

are

bae

- : unit = ()

The let _ = e syntax above is a way of evaluating e but not binding its value to any name. Indeed, we know the value
each of those print_endline functions will return: it will always be (), the unit value. So there’s no good reason to
bind it to a variable name. We could also write let () = e to indicate we know it’s just a unit value that we don’t care
about:

let () = print_endline "Camels" in


let () = print_endline "are" in
print_endline "bae"

58 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

Camels

are

bae

- : unit = ()

But either way the boilerplate of all the let..in is annoying to have to write! So there’s a special syntax that can
be used to chain together multiple functions that return unit. The expression e1; e2 first evaluates e1, which should
evaluate to (), then discards that value, and evaluates e2. So we could rewrite the above code as:

print_endline "Camels";
print_endline "are";
print_endline "bae"

Camels

are

bae

- : unit = ()

That is more idiomatic OCaml code, and it also looks more natural to imperative programmers.

Warning: There is no semicolon after the final print_endline in that example. A common mistake is to put
a semicolon after each print statement. Instead, the semicolons go strictly between statements. That is, semicolon is
a statement separator not a statement terminator. If you were to add a semicolon at the end, you could get a syntax
error depending on the surrounding code.

4.6.3 Ignore

If e1 does not have type unit, then e1; e2 will give a warning, because you are discarding a potentially useful value.
If that is truly your intent, you can call the built-in function ignore : 'a -> unit to convert any value to ():

(ignore 3); 5

- : int = 5

Actually ignore is easy to implement yourself:

let ignore x = ()

val ignore : 'a -> unit = <fun>

4.6. Printing 59
OCaml Programming: Correct + Efficient + Beautiful

Or you can even write underscore to indicate the function takes in a value but does not bind that value to a name. That
means the function can never use that value in its body. But that’s okay: we want to ignore it.

let ignore _ = ()

val ignore : 'a -> unit = <fun>

4.6.4 Printf

For complicated text outputs, using the built-in functions for primitive type printing quickly becomes tedious. For exam-
ple, suppose you wanted to write a function to print a statistic:

(** [print_stat name num] prints [name: num]. *)


let print_stat name num =
print_string name;
print_string ": ";
print_float num;
print_newline ()

val print_stat : string -> float -> unit = <fun>

print_stat "mean" 84.39

mean: 84.39

- : unit = ()

How could we shorten print_stat? In Java you might use the overloaded + operator to turn all objects into strings:

void print_stat(String name, double num) {


System.out.println(name + ": " + num);
}

But OCaml values are not objects, and they do not have a toString() method they inherit from some root Object
class. Nor does OCaml permit overloading of operators.
Long ago though, FORTRAN invented a different solution that other languages like C and Java and even Python support.
The idea is to use a format specifier to —as the name suggest— specify how to format output. The name this idea is best
known under is probably “printf”, which refers to the name of the C library function that implemented it. Many other
languages and libraries still use that name, including OCaml’s Printf module.
Here’s how we’d use printf to re-implement print_stat:

let print_stat name num =


Printf.printf "%s: %F\n%!" name num

val print_stat : string -> float -> unit = <fun>

print_stat "mean" 84.39

60 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

mean: 84.39

- : unit = ()

The first argument to function Printf.printf is the format specifier. It looks like a string, but there’s more to it than
that. It’s actually understood by the OCaml compiler in quite a deep way. Inside the format specifier there are:
• plain characters, and
• conversion specifiers, which begin with %.
There are about two dozen conversion specifiers available, which you can read about in the documentation of Printf.
Let’s pick apart the format specifier above as an example.
• It starts with "%s", which is the conversion specifier for strings. That means the next argument to printf must
be a string, and the contents of that string will be output.
• It continues with ": ", which are just plain characters. Those are inserted into the output.
• It then has another conversion specifier, %F. That means the next argument of printf must have type float,
and will be output in the same format that OCaml uses to print floats.
• The newline "\n" after that is another plain character sequence.
• Finally, the conversion specifier "%!" means to flush the output buffer. As you might have learned in earlier
programming classes, output is often buffered, meaning that it doesn’t all happen at once or right away. Flushing
the buffer ensures that anything still sitting in the buffer gets output immediately. This specifier is special in that it
doesn’t actually need another argument to printf.
If the type of an argument is incorrect with respect to the conversion specifier, OCaml will detect that. Let’s add a type
annotation to force num to be an int, and see what happens with the float conversion specifier %F:

let print_stat name (num : int) =


Printf.printf "%s: %F\n%!" name num

File "[14]", line 2, characters 34-37:


2 | Printf.printf "%s: %F\n%!" name num
^^^
Error: This expression has type int but an expression was expected of type
float

To fix that, we can change to the conversion specifier for int, which is %i:

let print_stat name num =


Printf.printf "%s: %i\n%!" name num

val print_stat : string -> int -> unit = <fun>

Another very useful variant of printf is sprintf, which collects the output in string instead of printing it:

let string_of_stat name num =


Printf.sprintf "%s: %F" name num

val string_of_stat : string -> float -> string = <fun>

4.6. Printing 61
OCaml Programming: Correct + Efficient + Beautiful

string_of_stat "mean" 84.39

- : string = "mean: 84.39"

4.7 Debugging

Debugging is a last resort when everything else has failed. Let’s take a step back and think about everything that comes
before debugging.

4.7.1 Defenses against Bugs

According to Rob Miller, there are four defenses against bugs:


1. The first defense against bugs is to make them impossible.
Entire classes of bugs can be eradicated by choosing to program in languages that guarantee memory safety (that no
part of memory can be accessed except through a pointer (or reference) that is valid for that region of memory) and
type safety (that no value can be used in a way inconsistent with its type). The OCaml type system, for example,
prevents programs from buffer overflows and meaningless operations (like adding a boolean to a float), whereas the
C type system does not.
2. The second defense against bugs is to use tools that find them.
There are automated source-code analysis tools, like FindBugs, which can find many common kinds of bugs in Java
programs, and SLAM, which is used to find bugs in device drivers. The subfield of CS known as formal methods
studies how to use mathematics to specify and verify programs, that is, how to prove that programs have no bugs.
We’ll study verification later in this course.
Social methods such as code reviews and pair programming are also useful tools for finding bugs. Studies at IBM
in the 1970s-1990s suggested that code reviews can be remarkably effective. In one study (Jones, 1991), code
inspection found 65% of the known coding errors and 25% of the known documentation errors, whereas testing
found only 20% of the coding errors and none of the documentation errors.
3. The third defense against bugs is to make them immediately visible.
The earlier a bug appears, the easier it is to diagnose and fix. If computation instead proceeds past the point of the
bug, then that further computation might obscure where the failure really occurred. Assertions in the source code
make programs “fail fast” and “fail loudly”, so that bugs appear immediately, and the programmer knows exactly
where in the source code to look.
4. The fourth defense against bugs is extensive testing.
How can you know whether a piece of code has a particular bug? Write tests that would expose the bug, then
confirm that your code doesn’t fail those tests. Unit tests for a relatively small piece of code, such as an individual
function or module, are especially important to write at the same time as you develop that code. Running of those
tests should be automated, so that if you ever break the code, you find out as soon as possible. (That’s really Defense
3 again.)
After all those defenses have failed, a programmer is forced to resort to debugging.

62 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

4.7.2 How to Debug

So you’ve discovered a bug. What next?


1. Distill the bug into a small test case. Debugging is hard work, but the smaller the test case, the more likely you
are to focus your attention on the piece of code where the bug lurks. Time spent on this distillation can therefore
be time saved, because you won’t have to re-read lots of code. Don’t continue debugging until you have a small test
case!
2. Employ the scientific method. Formulate a hypothesis as to why the bug is occurring. You might even write
down that hypothesis in a notebook, as if you were in a Chemistry lab, to clarify it in your own mind and keep track
of what hypotheses you’ve already considered. Next, design an experiment to affirm or deny that hypothesis. Run
your experiment and record the result. Based on what you’ve learned, reformulate your hypothesis. Continue until
you have rationally, scientifically determined the cause of the bug.
3. Fix the bug. The fix might be a simple correction of a typo. Or it might reveal a design flaw that causes you to
make major changes. Consider whether you might need to apply the fix to other locations in your code base—for
example, was it a copy and paste error? If so, do you need to refactor your code?
4. Permanently add the small test case to your test suite. You wouldn’t want the bug to creep back into your code
base. So keep track of that small test case by keeping it as part of your unit tests. That way, any time you make
future changes, you will automatically be guarding against that same bug. Repeatedly running tests distilled from
previous bugs is a part of regression testing.

4.7.3 Debugging in OCaml

Here are a couple tips on how to debug—if you are forced into it—in OCaml.
• Print statements. Insert a print statement to ascertain the value of a variable. Suppose you want to know what the
value of x is in the following function:

let inc x = x + 1

Just add the line below to print that value:

let inc x =
let () = print_int x in
x + 1

• Function traces. Suppose you want to see the trace of recursive calls and returns for a function. Use the #trace
directive:

# let rec fib x = if x <= 1 then 1 else fib (x - 1) + fib (x - 2);;


# #trace fib;;

If you evaluate fib 2, you will now see the following output:

fib <-- 2
fib <-- 0
fib --> 1
fib <-- 1
fib --> 1
fib --> 2

To stop tracing, use the #untrace directive.

4.7. Debugging 63
OCaml Programming: Correct + Efficient + Beautiful

• Debugger. OCaml has a debugging tool ocamldebug. You can find a tutorial on the OCaml website. Unless
you are using Emacs as your editor, you will probably find this tool to be harder to use than just inserting print
statements.

4.7.4 Defensive Programming

As we discussed earlier in the section on debugging, one defense against bugs is to make any bugs (or errors) immediately
visible. That idea connects with idea of preconditions.
Consider this specification of random_int:

(** [random_int bound] is a random integer between 0 (inclusive)


and [bound] (exclusive). Requires: [bound] is greater than 0
and less than 2^30. *)

If the client of random_int passes a value of bound that violates the “Requires” clause, such as -1, the implementation
of random_int is free to do anything whatsoever. All bets are off when the client violates the precondition.
But the most helpful thing for random_int to do is to immediately expose the fact that the precondition was violated.
After all, chances are that the client didn’t mean to violate it.
So the implementor of random_int would do well to check whether the precondition is violated, and if so, raise an
exception. Here are three possibilities of that kind of defensive programming:

(* possibility 1 *)
let random_int bound =
assert (bound > 0 && bound < 1 lsl 30);
(* proceed with the implementation of the function *)

(* possibility 2 *)
let random_int bound =
if not (bound > 0 && bound < 1 lsl 30)
then invalid_arg "bound";
(* proceed with the implementation of the function *)

(* possibility 3 *)
let random_int bound =
if not (bound > 0 && bound < 1 lsl 30)
then failwith "bound";
(* proceed with the implementation of the function *)

The second possibility is probably the most informative to the client, because it uses the built-in function invalid_arg
to raise the well-named exception Invalid_argument. In fact, that’s exactly what the standard library implementation
of this function does.
The first possibility is probably most useful when you are trying to debug your own code, rather than choosing to expose
a failed assertion to a client.
The third possibility differs from the second only in the name (Failure) of the exception that is raised. It might be
useful in situations where the precondition involves more than just a single invalid argument.
In this example, checking the precondition is computationally cheap. In other cases, it might require a lot of computa-
tion, so the implementer of the function might prefer not to check the precondition, or only to check some inexpensive
approximation to it.
Sometimes programmers worry unnecessarily that defensive programming will be too expensive—either in terms of the
time it costs them to implement the checks initially, or in the run-time costs that will be paid in checking assertions. These

64 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

concerns are far too often misplaced. The time and money it costs society to repair faults in software suggests that we
could all afford to have programs that run a little more slowly.
Finally, the implementer might even choose to eliminate the precondition and restate it as a postcondition:

(** [random_int bound] is a random integer between 0 (inclusive)


and [bound] (exclusive). Raises: [Invalid_argument "bound"]
unless [bound] is greater than 0 and less than 2^30. *)

Now instead of being free to do whatever when bound is too big or too small, random_int must raise an exception.
For this function, that’s probably the best choice.
In this course, we’re not going to force you to program defensively. But if you’re savvy, you’ll start (or continue) doing it
anyway. The small amount of time you spend coding up such defenses will save you hours of time in debugging, making
you a more productive programmer.

4.8 Summary

Syntax and semantics are a powerful paradigm for learning a programming language. As we learn the features of OCaml,
we’re being careful to write down their syntax and semantics. We’ve seen that there can be multiple syntaxes for expressing
the same semantic idea, that is, the same computation.
The semantics of function application is the very heart of OCaml and of functional programming, and it’s something we
will come back to several times throughout the course to deepen our understanding.

4.8.1 Terms and Concepts

• anonymous functions
• assertions
• binding
• binding expression
• body expression
• debugging
• defensive programming
• definitions
• documentation
• dynamic semantics
• evaluation
• expressions
• function application
• function definitions
• identifiers
• idioms
• if expressions

4.8. Summary 65
OCaml Programming: Correct + Efficient + Beautiful

• lambda expressions
• let definition
• let expression
• libraries
• metavariables
• mutual recursion
• pipeline operator
• postcondition
• precondition
• printing
• recursion
• semantics
• static semantics
• substitution
• syntax
• tools
• type checking
• type inference
• values

4.8.2 Further Reading

• Introduction to Objective Caml, chapter 3


• OCaml from the Very Beginning, chapter 2
• Real World OCaml, chapter 2
• Tail Recursion, The Musical. Tail-call optimization explained in the context of JavaScript with cute 8-bit animations,
and Disney songs!

4.9 Exercises

Solutions to most exercises are available. Fall 2022 is the first public release of these solutions. Though they have been
available to Cornell students for a few years, it is inevitable that wider circulation will reveal improvements that could be
made. We are happy to add or correct solutions. Please make contributions through GitHub.

Exercise: values [★]


What is the type and value of each of the following OCaml expressions?
• 7 * (1 + 2 + 3)
• "CS " ^ string_of_int 3110

66 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

Hint: type each expression into the toplevel and it will tell you the answer. Note: ^ is not exponentiation.

Exercise: operators [★★]


Examine the table of all operators in the OCaml manual (you will have to scroll down to find it on that page).
• Write an expression that multiplies 42 by 10.
• Write an expression that divides 3.14 by 2.0. Hint: integer and floating-point operators are written differently in
OCaml.
• Write an expression that computes 4.2 raised to the seventh power. Note: there is no built-in integer exponentiation
operator in OCaml (nor is there in C, by the way), in part because it is not an operation provided by most CPUs.

Exercise: equality [★]


• Write an expression that compares 42 to 42 using structural equality.
• Write an expression that compares "hi" to "hi" using structural equality. What is the result?
• Write an expression that compares "hi" to "hi" using physical equality. What is the result?

Exercise: assert [★]


• Enter assert true;; into utop and see what happens.
• Enter assert false;; into utop and see what happens.
• Write an expression that asserts 2110 is not (structurally) equal to 3110.

Exercise: if [★]
Write an if expression that evaluates to 42 if 2 is greater than 1 and otherwise evaluates to 7.

Exercise: double fun [★]


Using the increment function from above as a guide, define a function double that multiplies its input by 2. For example,
double 7 would be 14. Test your function by applying it to a few inputs. Turn those test cases into assertions.

Exercise: more fun [★★]


• Define a function that computes the cube of a floating-point number. Test your function by applying it to a few
inputs.
• Define a function that computes the sign (1, 0, or -1) of an integer. Use a nested if expression. Test your function
by applying it to a few inputs.
• Define a function that computes the area of a circle given its radius. Test your function with assert.
For the latter, bear in mind that floating-point arithmetic is not exact. Instead of asserting an exact value, you should
assert that the result is “close enough”, e.g., within 1e-5. If that’s unfamiliar to you, it would be worthwhile to read up on
floating-point arithmetic.
A function that take multiple inputs can be defined just by providing additional names for those inputs as part of the let
definition. For example, the following function computes the average of three arguments:

4.9. Exercises 67
OCaml Programming: Correct + Efficient + Beautiful

let avg3 x y z = (x +. y +. z) /. 3.

Exercise: RMS [★★]


Define a function that computes the root mean square of two numbers—i.e., √(𝑥2 + 𝑦2 )/2. Test your function with
assert.

Exercise: date fun [★★★]


Define a function that takes an integer d and string m as input and returns true just when d and m form a valid date. Here,
a valid date has a month that is one of the following abbreviations: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sept, Oct,
Nov, Dec. And the day must be a number that is between 1 and the minimum number of days in that month, inclusive.
For example, if the month is Jan, then the day is between 1 and 31, inclusive, whereas if the month is Feb, then the day
is between 1 and 28, inclusive.
How terse (i.e., few and short lines of code) can you make your function? You can definitely do this in fewer than 12
lines.

Exercise: fib [★★]


Define a recursive function fib : int -> int, such that fib n is the nth number in the Fibonacci sequence,
which is 1, 1, 2, 3, 5, 8, 13, … That is:
• fib 1 = 1,
• fib 2 = 1, and
• fib n = fib (n-1) + fib (n-2) for any n > 2.
Test your function in the toplevel.

Exercise: fib fast [★★★]


How quickly does your implementation of fib compute the 50th Fibonacci number? If it computes nearly instanta-
neously, congratulations! But the recursive solution most people come up with at first will seem to hang indefinitely.
The problem is that the obvious solution computes subproblems repeatedly. For example, computing fib 5 requires
computing both fib 3 and fib 4, and if those are computed separately, a lot of work (an exponential amount, in fact)
is being redone.
Create a function fib_fast that requires only a linear amount of work. Hint: write a recursive helper function h :
int -> int -> int -> int, where h n pp p is defined as follows:
• h 1 pp p = p, and
• h n pp p = h (n-1) p (pp+p) for any n > 1.
The idea of h is that it assumes the previous two Fibonacci numbers were pp and p, then computes forward n more
numbers. Hence, fib n = h n 0 1 for any n > 0.
What is the first value of n for which fib_fast n is negative, indicating that integer overflow occurred?

Exercise: poly types [★★★]


What is the type of each of the functions below? You can ask the toplevel to check your answers.

68 Chapter 4. The Basics of OCaml


OCaml Programming: Correct + Efficient + Beautiful

let f x = if x then x else x


let g x y = if y then x else x
let h x y z = if x then y else z
let i x y z = if x then y else y

Exercise: divide [★★]


Write a function divide : numerator:float -> denominator:float -> float. Apply your function.

Exercise: associativity [★★]


Suppose that we have defined let add x y = x + y. Which of the following produces an integer, which produces
a function, and which produces an error? Decide on an answer, then check your answer in the toplevel.
• add 5 1
• add 5
• (add 5) 1
• add (5 1)

Exercise: average [★★]


Define an infix operator +/. to compute the average of two floating-point numbers. For example,
• 1.0 +/. 2.0 = 1.5
• 0. +/. 0. = 0.

Exercise: hello world [★]


Type the following in utop:
• print_endline "Hello world!";;
• print_string "Hello world!";;
Notice the difference in output from each.

4.9. Exercises 69
OCaml Programming: Correct + Efficient + Beautiful

70 Chapter 4. The Basics of OCaml


Part III

OCaml Programming

71
CHAPTER

FIVE

DATA AND TYPES

In this chapter, we’ll examine some of OCaml’s built-in data types, including lists, variants, records, tuples, and options.
Many of those are likely to feel familiar from other programming languages. In particular,
• lists and tuples, might feel similar to Python; and
• records and variants, might feel similar to struct and enum types from C or Java.
Because of that familiarity, we call these standard data types. We’ll learn about pattern matching, which is a feature that’s
less likely to be familiar.
Almost immediately after we learn about lists, we’ll pause our study of standard data types to learn about unit testing in
OCaml with OUnit, a unit testing framework similar to those you might have used in other languages. OUnit relies on
lists, which is why we couldn’t cover it before now.
Later in the chapter, we study some OCaml data types that are unlikely to be as familiar from other languages. They
include:
• options, which are loosely related to null in Java;
• association lists, which are an amazingly simple implementation of maps (aka dictionaries) based on lists and
tuples;
• algebraic data types, which are arguably the most important kind of type in OCaml, and indeed are the power
behind many of the other built-in types; and
• exceptions, which are a special kind of algebraic data type.

5.1 Lists

An OCaml list is a sequence of values all of which have the same type. They are implemented as singly-linked lists. These
lists enjoy a first-class status in the language: there is special support for easily creating and working with lists. That’s a
characteristic that OCaml shares with many other functional languages. Mainstream imperative languages, like Python,
have such support these days too. Maybe that’s because programmers find it so pleasant to work directly with lists as a
first-class part of the language, rather than having to go through a library (as in C and Java).

73
OCaml Programming: Correct + Efficient + Beautiful

5.1.1 Building Lists

Syntax. There are three syntactic forms for building lists:

[]
e1 :: e2
[e1; e2; ...; en]

The empty list is written [] and is pronounced “nil”, a name that comes from Lisp. Given a list lst and element elt,
we can prepend elt to lst by writing elt :: lst. The double-colon operator is pronounced “cons”, a name that
comes from an operator in Lisp that constructs objects in memory. “Cons” can also be used as a verb, as in “I will cons an
element onto the list.” The first element of a list is usually called its head and the rest of the elements (if any) are called
its tail.
The square bracket syntax is convenient but unnecessary. Any list [e1; e2; ...; en] could instead be written
with the more primitive nil and cons syntax: e1 :: e2 :: ... :: en :: []. When a pleasant syntax can
be defined in terms of a more primitive syntax within the language, we call the pleasant syntax syntactic sugar: it makes
the language “sweeter”. Transforming the sweet syntax into the more primitive syntax is called desugaring.
Because the elements of the list can be arbitrary expressions, lists can be nested as deeply as we like, e.g., [[[]]; [[1;
2; 3]]].
Dynamic semantics.
• [] is already a value.
• If e1 evaluates to v1, and if e2 evaluates to v2, then e1 :: e2 evaluates to v1 :: v2.
As a consequence of those rules and how to desugar the square-bracket notation for lists, we have the following derived
rule:
• If ei evaluates to vi for all i in 1..n, then [e1; ...; en] evaluates to [v1; ...; vn].
It’s starting to get tedious to write “evaluates to” in all our evaluation rules. So let’s introduce a shorter notation for it.
We’ll write e ==> v to mean that e evaluates to v. Note that ==> is not a piece of OCaml syntax. Rather, it’s a notation
we use in our description of the language, kind of like metavariables. Using that notation, we can rewrite the latter two
rules above:
• If e1 ==> v1, and if e2 ==> v2, then e1 :: e2 ==> v1 :: v2.
• If ei ==> vi for all i in 1..n, then [e1; ...; en] ==> [v1; ...; vn].
Static semantics.
All the elements of a list must have the same type. If that element type is t, then the type of the list is t list. You
should read such types from right to left: t list is a list of t’s, t list list is a list of list of t’s, etc. The word
list itself here is not a type: there is no way to build an OCaml value that has type simply list. Rather, list is a
type constructor: given a type, it produces a new type. For example, given int, it produces the type int list. You
could think of type constructors as being like functions that operate on types, instead of functions that operate on values.
The type-checking rules:
• [] : 'a list
• If e1 : t and e2 : t list then e1 :: e2 : t list. In case the colons and their precedence is
confusing, the latter means (e1 :: e2) : t list.
In the rule for [], recall that 'a is a type variable: it stands for an unknown type. So the empty list is a list whose elements
have an unknown type. If we cons an int onto it, say 2 :: [], then the compiler infers that for that particular list,
'a must be int. But if in another place we cons a bool onto it, say true :: [], then the compiler infers that for
that particular list, 'a must be bool.

74 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.1.2 Accessing Lists

Note: The video linked above also uses records and tuples as examples. Those are covered in a later section of this book.

There are really only two ways to build a list, with nil and cons. So if we want to take apart a list into its component pieces,
we have to say what to do with the list if it’s empty, and what to do if it’s non-empty (that is, a cons of one element onto
some other list). We do that with a language feature called pattern matching.
Here’s an example of using pattern matching to compute the sum of a list:

let rec sum lst =


match lst with
| [] -> 0
| h :: t -> h + sum t

val sum : int list -> int = <fun>

This function says to take the input lst and see whether it has the same shape as the empty list. If so, return 0. Otherwise,
if it has the same shape as the list h :: t, then let h be the first element of lst, and let t be the rest of the elements of
lst, and return h + sum t. The choice of variable names here is meant to suggest “head” and “tail” and is a common
idiom, but we could use other names if we wanted. Another common idiom is:

let rec sum xs =


match xs with
| [] -> 0
| x :: xs' -> x + sum xs'

val sum : int list -> int = <fun>

That is, the input list is a list of xs (pronounced EX-uhs), the head element is an x, and the tail is xs’ (pronounced EX-uhs
prime).
Syntactically it isn’t necessary to use so many lines to define sum. We could do it all on one line:

let rec sum xs = match xs with | [] -> 0 | x :: xs' -> x + sum xs'

val sum : int list -> int = <fun>

Or, noting that the first | after with is optional regardless of how many lines we use, we could also write:

let rec sum xs = match xs with [] -> 0 | x :: xs' -> x + sum xs'

val sum : int list -> int = <fun>

The multi-line format is what we’ll usually use in this book, because it helps the human eye understand the syntax a bit
better. OCaml code formatting tools, though, are moving toward the single-line format whenever the code is short enough
to fit on just one line.
Here’s another example of using pattern matching to compute the length of a list:

5.1. Lists 75
OCaml Programming: Correct + Efficient + Beautiful

let rec length lst =


match lst with
| [] -> 0
| h :: t -> 1 + length t

val length : 'a list -> int = <fun>

Note how we didn’t actually need the variable h in the right-hand side of the pattern match. When we want to indicate
the presence of some value in a pattern without actually giving it a name, we can write _ (the underscore character):

let rec length lst =


match lst with
| [] -> 0
| _ :: t -> 1 + length t

val length : 'a list -> int = <fun>

That function is actually built-in as part of the OCaml standard library List module. Its name there is List.length.
That “dot” notation indicates the function named length inside the module named List, much like the dot notation
used in many other languages.
And here’s a third example that appends one list onto the beginning of another list:

let rec append lst1 lst2 =


match lst1 with
| [] -> lst2
| h :: t -> h :: append t lst2

val append : 'a list -> 'a list -> 'a list = <fun>

For example, append [1; 2] [3; 4] is [1; 2; 3; 4]. That function is actually available as a built-in operator
@, so we could instead write [1; 2] @ [3; 4].
As a final example, we could write a function to determine whether a list is empty:

let empty lst =


match lst with
| [] -> true
| h :: t -> false

val empty : 'a list -> bool = <fun>

But there is a much better way to write the same function without pattern matching:

let empty lst =


lst = []

val empty : 'a list -> bool = <fun>

Note how all the recursive functions above are similar to doing proofs by induction on the natural numbers: every natural
number is either 0 or is 1 greater than some other natural number 𝑛, and so a proof by induction has a base case for 0
and an inductive case for 𝑛 + 1. Likewise, all our functions have a base case for the empty list and a recursive case for

76 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

the list that has one more element than another list. This similarity is no accident. There is a deep relationship between
induction and recursion; we’ll explore that relationship in more detail later in the book.
By the way, there are two library functions List.hd and List.tl that return the head and tail of a list. It is not good,
idiomatic OCaml to apply these directly to a list. The problem is that they will raise an exception when applied to the
empty list, and you will have to remember to handle that exception. Instead, you should use pattern matching: you’ll then
be forced to match against both the empty list and the non-empty list (at least), which will prevent exceptions from being
raised, thus making your program more robust.

5.1.3 (Not) Mutating Lists

Lists are immutable. There’s no way to change an element of a list from one value to another. Instead, OCaml program-
mers create new lists out of old lists. For example, suppose we wanted to write a function that returned the same list as
its input list, but with the first element (if there is one) incremented by one. We could do that:

let inc_first lst =


match lst with
| [] -> []
| h :: t -> h + 1 :: t

Now you might be concerned about whether we’re being wasteful of space. After all, there are at least two ways the
compiler could implement the above code:
1. Copy the entire tail list t when the new list is created in the pattern match with cons, such that the amount of
memory in use just increased by an amount proportionate to the length of t.
2. Share the tail list t between the old list and the new list, such that the amount of memory in use does not increase—
beyond the one extra piece of memory needed to store h + 1.
In fact, the compiler does the latter. So there’s no need for concern. The reason that it’s quite safe for the compiler to
implement sharing is exactly that list elements are immutable. If they were instead mutable, then we’d start having to
worry about whether the list I have is shared with the list you have, and whether changes I make will be visible in your list.
So immutability makes it easier to reason about the code, and makes it safe for the compiler to perform an optimization.

5.1.4 Pattern Matching with Lists

We saw above how to access lists using pattern matching. Let’s look more carefully at this feature.
Syntax.

match e with
| p1 -> e1
| p2 -> e2
| ...
| pn -> en

Each of the clauses pi -> ei is called a branch or a case of the pattern match. The first vertical bar in the entire pattern
match is optional.
The p’s here are a new syntactic form called a pattern. For now, a pattern may be:
• a variable name, e.g., x
• the underscore character _, which is called the wildcard
• the empty list []
• p1 :: p2

5.1. Lists 77
OCaml Programming: Correct + Efficient + Beautiful

• [p1; ...; pn]


No variable name may appear more than once in a pattern. For example, the pattern x :: x is illegal. The wildcard
may occur any number of times.
As we learn more of data structures available in OCaml, we’ll expand the possibilities for what a pattern may be.
Dynamic semantics.
Pattern matching involves two inter-related tasks: determining whether a pattern matches a value, and determining what
parts of the value should be associated with which variable names in the pattern. The former task is intuitively about
determining whether a pattern and a value have the same shape. The latter task is about determining the variable bindings
introduced by the pattern. For example, consider the following code:

match 1 :: [] with
| [] -> false
| h :: t -> h >= 1 && List.length t = 0

- : bool = true

When evaluating the right-hand side of the second branch, h is bound to 1 and t is bound to []. Let’s write h->1 to
mean the variable binding saying that h has value 1; this is not a piece of OCaml syntax, but rather a notation we use to
reason about the language. So the variable bindings produced by the second branch would be h->1, t->[].
Using that notation, here is a definition of when a pattern matches a value and the bindings that match produces:
• The pattern x matches any value v and produces the variable binding x->v.
• The pattern _ matches any value and produces no bindings.
• The pattern [] matches the value [] and produces no bindings.
• If p1 matches v1 and produces a set 𝑏1 of bindings, and if p2 matches v2 and produces a set 𝑏2 of bindings, then
p1 :: p2 matches v1 :: v2 and produces the set 𝑏1 ∪ 𝑏2 of bindings. Note that v2 must be a list (since it’s
on the right-hand side of ::) and could have any length: 0 elements, 1 element, or many elements. Note that the
union 𝑏1 ∪ 𝑏2 of bindings will never have a problem where the same variable is bound separately in both 𝑏1 and 𝑏2
because of the syntactic restriction that no variable name may appear more than once in a pattern.
• If for all i in 1..n, it holds that pi matches vi and produces the set 𝑏𝑖 of bindings, then [p1; ...; pn]
matches [v1; ...; vn] and produces the set ⋃𝑖 𝑏𝑖 of bindings. Note that this pattern specifies the exact length
the list must be.
Now we can say how to evaluate match e with p1 -> e1 | ... | pn -> en:
• Evaluate e to a value v.
• Attempt to match v against p1, then against p2, and so on, in the order they appear in the match expression.
• If v does not match against any of the patterns, then evaluation of the match expression raises a Match_failure
exception. We haven’t yet discussed exceptions in OCaml, but you’re surely familiar with them from other lan-
guages. We’ll come back to exceptions near the end of this chapter, after we’ve covered some of the other built-in
data structures in OCaml.
• Otherwise, let pi be the first pattern that matches, and let 𝑏 be the variable bindings produced by matching v
against pi.
• Substitute those bindings 𝑏 inside ei, producing a new expression e'.
• Evaluate e' to a value v'.
• The result of the entire match expression is v'.
For example, here’s how this match expression would be evaluated:

78 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

match 1 :: [] with
| [] -> false
| h :: t -> h = 1 && t = []

- : bool = true

• 1 :: [] is already a value.
• [] does not match 1 :: [].
• h :: t does match 1 :: [] and produces variable bindings {h->1,t->[]}, because:
– h matches 1 and produces the variable binding h->1.
– t matches [] and produces the variable binding t->[].
• Substituting {h->1,t->[]} inside h = 1 && t = [] produces a new expression 1 = 1 && [] = [].
• Evaluating 1 = 1 && [] = [] yields the value true. We omit the justification for that fact here, but it follows
from other evaluation rules for built-in operators and function application.
• So the result of the entire match expression is true.
Static semantics.
• If e : ta and for all i, it holds that pi : ta and ei : tb, then (match e with p1 -> e1 | ...
| pn -> en) : tb.
That rule relies on being able to judge whether a pattern has a particular type. As usual, type inference comes into play
here. The OCaml compiler infers the types of any pattern variables as well as all occurrences of the wildcard pattern. As
for the list patterns, they have the same type-checking rules as list expressions.
Additional Static Checking.
In addition to that type-checking rule, there are two other checks the compiler does for each match expression.
First, exhaustiveness: the compiler checks to make sure that there are enough patterns to guarantee that at least one
of them matches the expression e, no matter what the value of that expression is at run time. This ensures that the
programmer did not forget any branches. For example, the function below will cause the compiler to emit a warning:

let head lst = match lst with h :: _ -> h

File "[12]", line 1, characters 15-41:

1 | let head lst = match lst with h :: _ -> h

^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning 8 [partial-match]: this pattern-matching is not exhaustive.

Here is an example of a case that is not matched:

[]

5.1. Lists 79
OCaml Programming: Correct + Efficient + Beautiful

val head : 'a list -> 'a = <fun>

By presenting that warning to the programmer, the compiler is helping the programmer to defend against the possibility
of Match_failure exceptions at runtime.

Note: Sorry about how the output from the cell above gets split into many lines in the HTML. That is currently an open
issue with JupyterBook, the framework used to build this book.

Second, unused branches: the compiler checks to see whether any of the branches could never be matched against
because one of the previous branches is guaranteed to succeed. For example, the function below will cause the compiler
to emit a warning:

let rec sum lst =


match lst with
| h :: t -> h + sum t
| [ h ] -> h
| [] -> 0

File "[13]", line 4, characters 4-9:

4 | | [ h ] -> h

^^^^^

Warning 11 [redundant-case]: this match case is unused.

val sum : int list -> int = <fun>

The second branch is unused because the first branch will match anything the second branch matches.
Unused match cases are usually a sign that the programmer wrote something other than what they intended. So by
presenting that warning, the compiler is helping the programmer to detect latent bugs in their code.
Here’s an example of one of the most common bugs that causes an unused match case warning. Understanding it is also
a good way to check your understanding of the dynamic semantics of match expressions:

let length_is lst n =


match List.length lst with
| n -> true
| _ -> false

File "[14]", line 4, characters 4-5:

4 | | _ -> false

Warning 11 [redundant-case]: this match case is unused.

80 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

val length_is : 'a list -> 'b -> bool = <fun>

The programmer was thinking that if the length of lst is equal to n, then this function will return true, and otherwise
will return false. But in fact this function always returns true. Why? Because the pattern variable n is distinct from
the function argument n. Suppose that the length of lst is 5. Then the pattern match becomes: match 5 with n
-> true | _ -> false. Does n match 5? Yes, according to the rules above: a variable pattern matches any value
and here produces the binding n->5. Then evaluation applies that binding to true, substituting all occurrences of n
inside of true with 5. Well, there are no such occurrences. So we’re done, and the result of evaluation is just true.
What the programmer really meant to write was:

let length_is lst n =


match List.length lst with
| m -> m = n

val length_is : 'a list -> int -> bool = <fun>

or better yet:

let length_is lst n =


List.length lst = n

val length_is : 'a list -> int -> bool = <fun>

5.1.5 Deep Pattern Matching

Patterns can be nested. Doing so can allow your code to look deeply into the structure of a list. For example:
• _ :: [] matches all lists with exactly one element
• _ :: _ matches all lists with at least one element
• _ :: _ :: [] matches all lists with exactly two elements
• _ :: _ :: _ :: _ matches all lists with at least three elements

5.1.6 Immediate Matches

When you have a function that immediately pattern-matches against its final argument, there’s a nice piece of syntactic
sugar you can use to avoid writing extra code. Here’s an example: instead of

let rec sum lst =


match lst with
| [] -> 0
| h :: t -> h + sum t

val sum : int list -> int = <fun>

you can write

5.1. Lists 81
OCaml Programming: Correct + Efficient + Beautiful

let rec sum = function


| [] -> 0
| h :: t -> h + sum t

val sum : int list -> int = <fun>

The word function is a keyword. Notice that we’re able to leave out the line containing match as well as the name
of the argument, which was never used anywhere else but that line. In such cases, though, it’s especially important in the
specification comment for the function to document what that argument is supposed to be, since the code no longer gives
it a descriptive name.

5.1.7 OCamldoc and List Syntax

OCamldoc is a documentation generator similar to Javadoc. It extracts comments from source code and produces HTML
(as well as other output formats). The standard library web documentation for the List module is generated by OCamldoc
from the standard library source code for that module, for example.

Warning: There is a syntactic convention with square brackets in OCamldoc that can be confusing with respect to
lists.
In an OCamldoc comment, source code is surrounded by square brackets. That code will be rendered in typewriter
face and syntax-highlighted in the output HTML. The square brackets in this case do not indicate a list.

For example, here is the comment for List.hd in the standard library source code:

(** Return the first element of the given list. Raise


[Failure "hd"] if the list is empty. *)

The [Failure "hd"] does not mean a list containing the exception Failure "hd". Rather it means to typeset
the expression Failure "hd" as source code, as you can see here.
This can get especially confusing when you want to talk about lists as part of the documentation. For example, here is a
way we could rewrite that comment:

(** [hd lst] returns the first element of [lst].


Raises [Failure "hd"] if [lst = []]. *)

In [lst = []], the outer square brackets indicate source code as part of a comment, whereas the inner square brackets
indicate the empty list.

5.1.8 List Comprehensions

Some languages, including Python and Haskell, have a syntax called comprehension that allows lists to be written somewhat
like set comprehensions from mathematics. The earliest example of comprehensions seems to be the functional language
NPL, which was designed in 1977.
OCaml doesn’t have built-in syntactic support for comprehensions. Though some extensions were developed, none seem
to be supported any longer. The primary tasks accomplished by comprehensions (filtering out some elements, and trans-
forming others) are actually well-supported already by higher-order programming, which we’ll study in a later chapter, and
the pipeline operator, which we’ve already learned. So an additional syntax for comprehensions was never really needed.

82 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.1.9 Tail Recursion

Recall that a function is tail recursive if it calls itself recursively but does not perform any computation after the recursive
call returns, and immediately returns to its caller the value of its recursive call. Consider these two implementations, sum
and sum_tr of summing a list:

let rec sum (l : int list) : int =


match l with
| [] -> 0
| x :: xs -> x + (sum xs)

let rec sum_plus_acc (acc : int) (l : int list) : int =


match l with
| [] -> acc
| x :: xs -> sum_plus_acc (acc + x) xs

let sum_tr : int list -> int =


sum_plus_acc 0

val sum : int list -> int = <fun>

val sum_plus_acc : int -> int list -> int = <fun>

val sum_tr : int list -> int = <fun>

Observe the following difference between the sum and sum_tr functions above: In the sum function, which is not
tail recursive, after the recursive call returned its value, we add x to it. In the tail recursive sum_tr, or rather in
sum_plus_acc, after the recursive call returns, we immediately return the value without further computation.
If you’re going to write functions on really long lists, tail recursion becomes important for performance. So when you have
a choice between using a tail-recursive vs. non-tail-recursive function, you are likely better off using the tail-recursive
function on really long lists to achieve space efficiency. For that reason, the List module documents which functions are
tail recursive and which are not.
But that doesn’t mean that a tail-recursive implementation is strictly better. For example, the tail-recursive function might
be harder to read. (Consider sum_plus_acc.) Also, there are cases where implementing a tail-recursive function
entails having to do a pre- or post-processing pass to reverse the list. On small- to medium-sized lists, the overhead of
reversing the list (both in time and in allocating memory for the reversed list) can make the tail-recursive version less time
efficient. What constitutes “small” vs. “big” here? That’s hard to say, but maybe 10,000 is a good estimate, according to
the standard library documentation of the List module.
Here is a useful tail-recursive function to produce a long list:

(** [from i j l] is the list containing the integers from [i] to [j],
inclusive, followed by the list [l].
Example: [from 1 3 [0] = [1; 2; 3; 0]] *)
let rec from i j l = if i > j then l else from i (j - 1) (j :: l)

(** [i -- j] is the list containing the integers from [i] to [j], inclusive. *)
let ( -- ) i j = from i j []

let long_list = 0 -- 1_000_000

val from : int -> int -> int list -> int list = <fun>

5.1. Lists 83
OCaml Programming: Correct + Efficient + Beautiful

val ( -- ) : int -> int -> int list = <fun>

val long_list : int list =


[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20;
21; 22; 23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38;
39; 40; 41; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56;
57; 58; 59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74;
75; 76; 77; 78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92;
93; 94; 95; 96; 97; 98; 99; 100; 101; 102; 103; 104; 105; 106; 107; 108;
109; 110; 111; 112; 113; 114; 115; 116; 117; 118; 119; 120; 121; 122; 123;
124; 125; 126; 127; 128; 129; 130; 131; 132; 133; 134; 135; 136; 137; 138;
139; 140; 141; 142; 143; 144; 145; 146; 147; 148; 149; 150; 151; 152; 153;
154; 155; 156; 157; 158; 159; 160; 161; 162; 163; 164; 165; 166; 167; 168;
169; 170; 171; 172; 173; 174; 175; 176; 177; 178; 179; 180; 181; 182; 183;
184; 185; 186; 187; 188; 189; 190; 191; 192; 193; 194; 195; 196; 197; 198;
199; 200; 201; 202; 203; 204; 205; 206; 207; 208; 209; 210; 211; 212; 213;
214; 215; 216; 217; 218; 219; 220; 221; 222; 223; 224; 225; 226; 227; 228;
229; 230; 231; 232; 233; 234; 235; 236; 237; 238; 239; 240; 241; 242; 243;
244; 245; 246; 247; 248; 249; 250; 251; 252; 253; 254; 255; 256; 257; 258;
259; 260; 261; 262; 263; 264; 265; 266; 267; 268; 269; 270; 271; 272; 273;
274; 275; 276; 277; 278; 279; 280; 281; 282; 283; 284; 285; 286; 287; 288;
289; 290; 291; 292; 293; 294; 295; 296; 297; 298; ...]

It would be worthwhile to study the definition of -- to convince yourself that you understand (i) how it works and (ii)
why it is tail recursive.
You might in the future decide you want to create such a list again. Rather than having to remember where this definition
is, and having to copy it into your code, here’s an easy way to create the same list using a built-in library function:

List.init 1_000_000 Fun.id

- : int list =
[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20;
21; 22; 23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39;
40; 41; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58;
59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77;
78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96;
97; 98; 99; 100; 101; 102; 103; 104; 105; 106; 107; 108; 109; 110; 111; 112;
113; 114; 115; 116; 117; 118; 119; 120; 121; 122; 123; 124; 125; 126; 127;
128; 129; 130; 131; 132; 133; 134; 135; 136; 137; 138; 139; 140; 141; 142;
143; 144; 145; 146; 147; 148; 149; 150; 151; 152; 153; 154; 155; 156; 157;
158; 159; 160; 161; 162; 163; 164; 165; 166; 167; 168; 169; 170; 171; 172;
173; 174; 175; 176; 177; 178; 179; 180; 181; 182; 183; 184; 185; 186; 187;
188; 189; 190; 191; 192; 193; 194; 195; 196; 197; 198; 199; 200; 201; 202;
203; 204; 205; 206; 207; 208; 209; 210; 211; 212; 213; 214; 215; 216; 217;
218; 219; 220; 221; 222; 223; 224; 225; 226; 227; 228; 229; 230; 231; 232;
233; 234; 235; 236; 237; 238; 239; 240; 241; 242; 243; 244; 245; 246; 247;
248; 249; 250; 251; 252; 253; 254; 255; 256; 257; 258; 259; 260; 261; 262;
263; 264; 265; 266; 267; 268; 269; 270; 271; 272; 273; 274; 275; 276; 277;
278; 279; 280; 281; 282; 283; 284; 285; 286; 287; 288; 289; 290; 291; 292;
293; 294; 295; 296; 297; 298; ...]

Expression List.init len f creates the list [f 0; f 1; ...; f (len - 1)], and it does so tail recursively
if len is bigger than 10,000. Function Fun.id is simply the identify function fun x -> x.

84 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.2 Variants

A variant is a data type representing a value that is one of several possibilities. At their simplest, variants are like enums
from C or Java:

type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat


let d = Tue

type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

The individual names of the values of a variant are called constructors in OCaml. In the example above, the constructors
are Sun, Mon, etc. This is a somewhat different use of the word constructor than in C++ or Java.
For each kind of data type in OCaml, we’ve been discussing how to build and access it. For variants, building is easy:
just write the name of the constructor. For accessing, we use pattern matching. For example:

let int_of_day d =
match d with
| Sun -> 1
| Mon -> 2
| Tue -> 3
| Wed -> 4
| Thu -> 5
| Fri -> 6
| Sat -> 7

val int_of_day : day -> int = <fun>

There isn’t any kind of automatic way of mapping a constructor name to an int, like you might expect from languages
with enums.
Syntax.
Defining a variant type:

type t = C1 | ... | Cn

The constructor names must begin with an uppercase letter. OCaml uses that to distinguish constructors from variable
identifiers.
The syntax for writing a constructor value is simply its name, e.g., C.
Dynamic semantics.
• A constructor is already a value. There is no computation to perform.
Static semantics.
• If t is a type defined as type t = ... | C | ..., then C : t.

5.2. Variants 85
OCaml Programming: Correct + Efficient + Beautiful

5.2.1 Scope

Suppose there are two types defined with overlapping constructor names, for example,

type t1 = C | D
type t2 = D | E
let x = D

type t1 = C | D

type t2 = D | E

val x : t2 = D

When D appears after these definitions, to which type does it refer? That is, what is the type of x above? The answer is
that the type defined later wins. So x : t2. That is potentially surprising to programmers, so within any given scope
(e.g., a file or a module, though we haven’t covered modules yet) it’s idiomatic whenever overlapping constructor names
might occur to prefix them with some distinguishing character. For example, suppose we’re defining types to represent
Pokémon:

type ptype =
TNormal | TFire | TWater

type peff =
ENormal | ENotVery | ESuper

type ptype = TNormal | TFire | TWater

type peff = ENormal | ENotVery | ESuper

Because “Normal” would naturally be a constructor name for both the type of a Pokémon and the effectiveness of a Poké-
mon attack, we add an extra character in front of each constructor name to indicate whether it’s a type or an effectiveness.

5.2.2 Pattern Matching

Each time we introduced a new kind of data type, we need to introduce the new patterns associated with it. For variants,
this is easy. We add the following new pattern form to the list of legal patterns:
• a constructor name C
And we extend the definition of when a pattern matches a value and produces a binding as follows:
• The pattern C matches the value C and produces no bindings.

Note: Variants are considerably more powerful than what we have seen here. We’ll return to them again soon.

86 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.3 Unit Testing with OUnit

Note: This section is a bit of a detour from our study of data types, but it’s a good place to take the detour: we now
know just enough to understand how unit testing can be done in OCaml, and there’s no good reason to wait any longer to
learn about it.

Using the toplevel to test functions will only work for very small programs. Larger programs need test suites that contain
many unit tests and can be re-run every time we update our code base. A unit test is a test of one small piece of functionality
in a program, such as an individual function.
We’ve now learned enough features of OCaml to see how to do unit testing with a library called OUnit. It is a unit testing
framework similar to JUnit in Java, HUnit in Haskell, etc. The basic workflow for using OUnit is as follows:
• Write a function in a file f.ml. There could be many other functions in that file too.
• Write unit tests for that function in a separate file test.ml. That exact name is not actually essential.
• Build and run test to execute the unit tests.
The OUnit documentation is available on GitHub.

5.3.1 An Example of OUnit

The following example shows you how to create an OUnit test suite. There are some things in the example that might at
first seem mysterious; they are discussed in the next section.
Create a new directory. In that directory, create a file named sum.ml, and put the following code into it:

let rec sum = function


| [] -> 0
| x :: xs -> x + sum xs

Now create a second file named test.ml, and put this code into it:

open OUnit2
open Sum

let tests = "test suite for sum" >::: [


"empty" >:: (fun _ -> assert_equal 0 (sum []));
"singleton" >:: (fun _ -> assert_equal 1 (sum [1]));
"two_elements" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]

let _ = run_test_tt_main tests

Depending on your editor and its configuration, you probably now see some “Unbound module” errors about OUnit2 and
Sum. Don’t worry; the code is actually correct. We just need to set up dune and tell it to link OUnit. Create a dune file
and put this in it:

(executable
(name test)
(libraries ounit2))

And create a dune-project file as usual:

5.3. Unit Testing with OUnit 87


OCaml Programming: Correct + Efficient + Beautiful

(lang dune 3.4)

Now build the test suite:

$ dune build test.exe

Go back to your editor and do anything that will cause it to revisit test.ml. You can close and re-open the window, or
make a trivial change in the file (e.g., add then delete a space). Now the errors should all disappear.
Finally, you can run the test suite:

$ dune exec ./test.exe

You will get a response something like this:

...
Ran: 3 tests in: 0.12 seconds.
OK

Now suppose we modify sum.ml to introduce a bug by changing the code in it to the following:

let rec sum = function


| [] -> 1 (* bug *)
| x :: xs -> x + sum xs

If rebuild and re-execute the test suite, all test cases now fail. The output tells us the names of the failing cases. Here’s the
beginning of the output, in which we’ve replaced some strings that will be dependent on your own local computer with
...:

FFF
==============================================================================
Error: test suite for sum:2:two_elements.

File ".../_build/oUnit-test suite for sum-...#01.log", line 9, characters 1-1:


Error: test suite for sum:2:two_elements (in the log).

Raised at OUnitAssert.assert_failure in file "src/lib/ounit2/advanced/oUnitAssert.ml",


↪ line 45, characters 2-27

Called from OUnitRunner.run_one_test.(fun) in file "src/lib/ounit2/advanced/


↪oUnitRunner.ml", line 83, characters 13-26

not equal
------------------------------------------------------------------------------

The first line of that output

FFF

tells us that OUnit ran three test cases and all three failed.
The next interesting line

Error: test suite for sum:2:two_elements.

tells us that in the test suite named test suite for sum the test case at index 2 named two_elements failed.
The rest of the output for that test case is not particularly interesting; let’s ignore it for now.

88 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.3.2 Explanation of the OUnit Example

Let’s study more carefully what we just did in the previous section. In the test file, open OUnit2 brings into scope the
many definitions in OUnit2, which is version 2 of the OUnit framework. And open Sum brings into scope the definitions
from sum.ml. We’ll learn more about scope and the open keyword later in a later chapter.
Then we created a list of test cases:

[
"empty" >:: (fun _ -> assert_equal 0 (sum []));
"one" >:: (fun _ -> assert_equal 1 (sum [1]));
"onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]

Each line of code is a separate test case. A test case has a string giving it a descriptive name, and a function to run as
the test case. In between the name and the function we write >::, which is a custom operator defined by the OUnit
framework. Let’s look at the first function from above:

fun _ -> assert_equal 0 (sum [])

Every test case function receives as input a parameter that OUnit calls a test context. Here (and in many of the test cases
we write) we don’t actually need to worry about the context, so we use the underscore to indicate that the function ignores
its input. The function then calls assert_equal, which is a function provided by OUnit that checks to see whether its
two arguments are equal. If so the test case succeeds. If not, the test case fails.
Then we created a test suite:

let tests = "test suite for sum" >::: [


"empty" >:: (fun _ -> assert_equal 0 (sum []));
"singleton" >:: (fun _ -> assert_equal 1 (sum [1]));
"two_elements" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]

The >::: operator is another custom OUnit operator. It goes between the name of the test suite and the list of test cases
in that suite.
Then we ran the test suite:

let _ = run_test_tt_main tests

The function run_test_tt_main is provided by OUnit. It runs a test suite and prints the results of which test cases
passed vs. which failed to standard output. The use of let _ = here indicates that we don’t care what value the
function returns; it just gets discarded.

5.3.3 Improving OUnit Output

In our example with the buggy implementation of sum, we got the following output:

==============================================================================
Error: test suite for sum:2:two_elements.
...
not equal
------------------------------------------------------------------------------

5.3. Unit Testing with OUnit 89


OCaml Programming: Correct + Efficient + Beautiful

The not equal in the OUnit output means that assert_equal discovered the two values passed to it in that test
case were not equal. That’s not so informative: we’d like to know why they’re not equal. In particular, we’d like to know
what the actual output produced by sum was for that test case. To find out, we need to pass an additional argument
to assert_equal. That argument, whose label is printer, should be a function that can transform the outputs to
strings. In this case, the outputs are integers, so string_of_int from the Stdlib module will suffice. We modify
the test suite as follows:

let tests = "test suite for sum" >::: [


"empty" >:: (fun _ -> assert_equal 0 (sum []) ~printer:string_of_int);
"singleton" >:: (fun _ -> assert_equal 1 (sum [1]) ~printer:string_of_int);
"two_elements" >:: (fun _ -> assert_equal 3 (sum [1; 2]) ~printer:string_of_int);
]

And now we get more informative output:

==============================================================================
Error: test suite for sum:2:two_elements.
...
expected: 3 but got: 4
------------------------------------------------------------------------------

That output means that the test named two_elements asserted the equality of 3 and 4. The expected output was 3
because that was the first input to assert_equal, and that function’s specification says that in assert_equal x y,
the output you (as the tester) are expecting to get should be x, and the output the function being tested actually produces
should be y.
Notice how our test suite is accumulating a lot of redundant code. In particular, we had to add the printer argument
to several lines. Let’s improve that code by factoring out a function that constructs test cases:

let make_sum_test name expected_output input =


name >:: (fun _ -> assert_equal expected_output (sum input) ~printer:string_of_int)

let tests = "test suite for sum" >::: [


make_sum_test "empty" 0 [];
make_sum_test "singleton" 1 [1];
make_sum_test "two_elements" 3 [1; 2];
]

For output types that are more complicated than integers, you will end up needing to write your own functions to pass
to printer. This is similar to writing toString() methods in Java: for complicated types you invent yourself, the
language doesn’t know how to render them as strings. You have to provide the code that does it.

90 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.3.4 Testing for Exceptions

We have a little more of OCaml to learn before we can see how to test for exceptions. You can peek ahead to the section
on exceptions if you want to know now.

5.3.5 Test-Driven Development

Testing doesn’t have to happen strictly after you write code. In test-driven development (TDD), testing comes first! It
emphasizes incremental development of code: there is always something that can be tested. Testing is not something that
happens after implementation; instead, continuous testing is used to catch errors early. Thus, it is important to develop unit
tests immediately when the code is written. Automating test suites is crucial so that continuous testing requires essentially
no effort.
Here’s an example of TDD. We deliberately choose an exceedingly simple function to implement, so that the process is
clear. Suppose we are working with a data type for days:

type day = Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday

And we want to write a function next_weekday : day -> day that returns the next weekday after a given day.
We start by writing the most basic, broken version of that function we can:

let next_weekday d = failwith "Unimplemented"

Note: The built-in function failwith raises an exception along with the error message passed to the function.

Then we write the simplest unit test we can imagine. For example, we know that the next weekday after Monday is
Tuesday. So we add a test:

let tests = "test suite for next_weekday" >::: [


"tue_after_mon" >:: (fun _ -> assert_equal Tuesday (next_weekday Monday));
]

Then we run the OUnit test suite. It fails, as expected. That’s good! Now we have a concrete goal, to make that unit test
pass. We revise next_weekday to make that happen:

let next_weekday d =
match d with
| Monday -> Tuesday
| _ -> failwith "Unimplemented"

We compile and run the test; it passes. Time to add some more tests. The simplest remaining possibilities are tests
involving just weekdays, rather than weekends. So let’s add tests for weekdays.

let tests = "test suite for next_weekday" >::: [


"tue_after_mon" >:: (fun _ -> assert_equal Tuesday (next_weekday Monday));
"wed_after_tue" >:: (fun _ -> assert_equal Wednesday (next_weekday Tuesday));
"thu_after_wed" >:: (fun _ -> assert_equal Thursday(next_weekday Wednesday));
"fri_after_thu" >:: (fun _ -> assert_equal Friday (next_weekday Thursday));
]

We compile and run the tests; many fail. That’s good! We add new functionality:

5.3. Unit Testing with OUnit 91


OCaml Programming: Correct + Efficient + Beautiful

let next_weekday d =
match d with
| Monday -> Tuesday
| Tuesday -> Wednesday
| Wednesday -> Thursday
| Thursday -> Friday
| _ -> failwith "Unimplemented"

We compile and run the tests; they pass. At this point we could move on to handling weekends, but we should first notice
something about the tests we’ve written: they involve repeating a lot of code. In fact, we probably wrote them by copying-
and-pasting the first test, then modifying it for the next three. That’s a sign that we should refactor the code. (As we did
before with the sum function we were testing.)
Let’s abstract a function that creates test cases for next_weekday:

let make_next_weekday_test name expected_output input =


name >:: (fun _ -> assert_equal expected_output (next_weekday input))

let tests = "test suite for next_weekday" >::: [


make_next_weekday_test "tue_after_mon" Tuesday Monday;
make_next_weekday_test "wed_after_tue" Wednesday Tuesday;
make_next_weekday_test "thu_after_wed" Thursday Wednesday;
make_next_weekday_test "fri_after_thu" Friday Thursday;
]

Now we finish the testing and implementation by handling weekends. First we add some test cases:

...
make_next_weekday_test "mon_after_fri" Monday Friday;
make_next_weekday_test "mon_after_sat" Monday Saturday;
make_next_weekday_test "mon_after_sun" Monday Sunday;
...

Then we finish the function:

let next_weekday d =
match d with
| Monday -> Tuesday
| Tuesday -> Wednesday
| Wednesday -> Thursday
| Thursday -> Friday
| Friday -> Monday
| Saturday -> Monday
| Sunday -> Monday

Of course, most people could write that function without errors even if they didn’t use TDD. But rarely do we implement
functions that are so simple.
Process. Let’s review the process of TDD:
• Write a failing unit test case. Run the test suite to prove that the test case fails.
• Implement just enough functionality to make the test case pass. Run the test suite to prove that the test case passes.
• Improve code as needed. In the example above we refactored the test suite, but often we’ll need to refactor the
functionality being implemented.
• Repeat until you are satisfied that the test suite provides evidence that your implementation is correct.

92 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.4 Records and Tuples

Singly-linked lists are a great data structure, but what if you want a fixed number of elements, instead of an unbounded
number? Or what if you want the elements to have distinct types? Or what if you want to access the elements by name
instead of by number? Lists don’t make any of those possibilities easy. Instead, OCaml programmers use records and
tuples.

5.4.1 Records

A record is a composite of other types of data, each of which is named. OCaml records are much like structs in C. Here’s
an example of a record type definition mon for a Pokémon, re-using the ptype definition from the variants section:

type ptype = TNormal | TFire | TWater


type mon = {name : string; hp : int; ptype : ptype}

type ptype = TNormal | TFire | TWater

type mon = { name : string; hp : int; ptype : ptype; }

This type defines a record with three fields named name, hp (hit points), and ptype. The type of each of those fields
is also given. Note that ptype can be used as both a type name and a field name; the namespace for those is distinct in
OCaml.
To build a value of a record type, we write a record expression, which looks like this:

{name = "Charmander"; hp = 39; ptype = TFire}

- : mon = {name = "Charmander"; hp = 39; ptype = TFire}

So in a type definition we write a colon between the name and the type of a field, but in an expression we write an equals
sign.
To access a record and get a field from it, we use the dot notation that you would expect from many other languages. For
example:

let c = {name = "Charmander"; hp = 39; ptype = TFire};;


c.hp

val c : mon = {name = "Charmander"; hp = 39; ptype = TFire}

- : int = 39

It’s also possible to use pattern matching to access record fields:

match c with {name = n; hp = h; ptype = t} -> h

- : int = 39

The n, h, and t here are pattern variables. There is a syntactic sugar provided if you want to use the same name for both
the field and a pattern variable:

5.4. Records and Tuples 93


OCaml Programming: Correct + Efficient + Beautiful

match c with {name; hp; ptype} -> hp

- : int = 39

Here, the pattern {name; hp; ptype} is sugar for {name = name; hp = hp; ptype = ptype}. In
each of those subexpressions, the identifier appearing on the left-hand side of the equals is a field name, and the identifier
appearing on the right-hand side is a pattern variable.
Syntax.
A record expression is written:

{f1 = e1; ...; fn = en}

The order of the fi=ei inside a record expression is irrelevant. For example, {f = e1; g = e2} is entirely
equivalent to {g = e2; f = e1}.
A field access is written:

e.f

where f must be an identifier of a field name, not an expression. That restriction is the same as in any other language
with similar features——for example, Java field names. If you really do want to compute which identifier to access, then
actually you want a different data structure: a map (also known by many other names: a dictionary or association list or
hash table etc., though there are subtle differences implied by each of those terms.)
Dynamic semantics.
• If for all i in 1..n, it holds that ei ==> vi, then {f1 = e1; ...; fn = en} ==> {f1 = v1;
...; fn = vn}.
• If e ==> {...; f = v; ...} then e.f ==> v.
Static semantics.
A record type is written:

{f1 : t1; ...; fn : tn}

The order of the fi:ti inside a record type is irrelevant. For example, {f : t1; g : t2} is entirely equivalent
to {g:t2;f:t1}.
Note that record types must be defined before they can be used. This enables OCaml to do better type inference than
would be possible if record types could be used without definition.
The type checking rules are:
• If for all i in 1..n, it holds that ei : ti, and if t is defined to be {f1 : t1; ...; fn : tn}, then
{f1 = e1; ...; fn = en} : t. Note that the set of fields provided in a record expression must be the
full set of fields defined as part of the record’s type (but see below regarding record copy).
• If e : t1 and if t1 is defined to be {...; f : t2; ...}, then e.f : t2.
Record copy.
Another syntax is also provided to construct a new record out of an old record:

{e with f1 = e1; ...; fn = en}

94 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

This doesn’t mutate the old record. Rather, it constructs a new record with new values. The set of fields provided after
the with does not have to be the full set of fields defined as part of the record’s type. In the newly-copied record, any
field not provided as part of the with is copied from the old record.
Record copy is syntactic sugar. It’s equivalent to writing

{ f1 = e1; ...; fn = en;


g1 = e.g1; ...; gn = e.gn }

where the set of gi is the set of all fields of the record’s type minus the set of fi.
Pattern matching.
We add the following new pattern form to the list of legal patterns:
• {f1 = p1; ...; fn = pn}
And we extend the definition of when a pattern matches a value and produces a binding as follows:
• If for all i in 1..n, it holds that pi matches vi and produces bindings 𝑏𝑖 , then the record pattern {f1 = p1;
...; fn = pn} matches the record value {f1 = v1; ...; fn = vn; ...} and produces the set ⋃𝑖 𝑏𝑖
of bindings. Note that the record value may have more fields than the record pattern does.
As a syntactic sugar, another form of record pattern is provided: {f1; ...; fn}. It is desugared to {f1 = f1;
...; fn = fn}.

5.4.2 Tuples

Like records, tuples are a composite of other types of data. But instead of naming the components, they are identified by
position. Here are some examples of tuples:

(1, 2, 10)
(true, "Hello")
([1; 2; 3], (0.5, 'X'))

A tuple with two components is called a pair. A tuple with three components is called a triple. Beyond that, we usually
just use the word tuple instead of continuing a naming scheme based on numbers.

Tip: Beyond about three components, it’s arguably better to use records instead of tuples, because it becomes hard for
a programmer to remember which component was supposed to represent what information.

Building of tuples is easy: just write the tuple, as above. Accessing again involves pattern matching, for example:

match (1, 2, 3) with (x, y, z) -> x + y + z

- : int = 6

Syntax.
A tuple is written

(e1, e2, ..., en)

The parentheses are not entirely mandatory —often your code can successfully parse without them— but they are usually
considered to be good style to include.

5.4. Records and Tuples 95


OCaml Programming: Correct + Efficient + Beautiful

Dynamic semantics.
• If for all i in 1..n it holds that ei ==> vi, then (e1, ..., en) ==> (v1, ..., vn).
Static semantics.
Tuple types are written using a new type constructor *, which is different than the multiplication operator. The type t1
* ... * tn is the type of tuples whose first component has type t1, …, and nth component has type tn.
• If for all i in 1..n it holds that ei : ti, then (e1, ..., en) : t1 * ... * tn.
Pattern matching.
We add the following new pattern form to the list of legal patterns:
• (p1, ..., pn)
The parentheses are again not entirely mandatory but usually are idiomatic to include.
And we extend the definition of when a pattern matches a value and produces a binding as follows:
• If for all i in 1..n, it holds that pi matches vi and produces bindings 𝑏𝑖 , then the tuple pattern (p1, ...,
pn) matches the tuple value (v1, ..., vn) and produces the set ⋃𝑖 𝑏𝑖 of bindings. Note that the tuple value
must have exactly the same number of components as the tuple pattern does.

5.4.3 Variants vs. Tuples and Records

Note: The second video above uses more advanced examples of variants that will be studied in a later section.

The big difference between variants and the types we just learned (records and tuples) is that a value of a variant type is
one of a set of possibilities, whereas a value of a tuple or record type provides each of a set of possibilities. Going back
to our examples, a value of type day is one of Sun or Mon or etc. But a value of type mon provides each of a string
and an int and ptype. Note how, in those previous two sentences, the word “or” is associated with variant types, and
the word “and” is associated with tuple and record types. That’s a good clue if you’re ever trying to decide whether you
want to use a variant, or a tuple or record: if you need one piece of data or another, you want a variant; if you need one
piece of data and another, you want a tuple or record.
One-of types are more commonly known as sum types, and each-of types as product types. Those names come from set
theory. Variants are like disjoint union, because each value of a variant comes from one of many underlying sets (and thus
far each of those sets is just a single constructor hence has cardinality one). Disjoint union is indeed sometimes written
with a summation operator Σ. Tuples/records are like Cartesian product, because each value of a tuple or record contains
a value from each of many underlying sets. Cartesian product is usually written with a product operator, × or Π.

5.5 Advanced Pattern Matching

Here are some additional pattern forms that are useful:


• p1 | ... | pn: an “or” pattern; matching against it succeeds if a match succeeds against any of the individual
patterns pi, which are tried in order from left to right. All the patterns must bind the same variables.
• (p : t): a pattern with an explicit type annotation.
• c: here, c means any constant, such as integer literals, string literals, and booleans.
• 'ch1'..'ch2': here, ch means a character literal. For example, 'A'..'Z' matches any uppercase letter.
• p when e: matches p but only if e evaluates to true.

96 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

You can read about all the pattern forms in the manual.

5.5.1 Pattern Matching with Let

The syntax we’ve been using so far for let expressions is, in fact, a special case of the full syntax that OCaml permits.
That syntax is:

let p = e1 in e2

That is, the left-hand side of the binding may in fact be a pattern, not just an identifier. Of course, variable identifiers are
on our list of valid patterns, so that’s why the syntax we’ve studied so far is just a special case.
Given this syntax, we revisit the semantics of let expressions.
Dynamic semantics.
To evaluate let p = e1 in e2:
1. Evaluate e1 to a value v1.
2. Match v1 against pattern p. If it doesn’t match, raise the exception Match_failure. Otherwise, if it does
match, it produces a set 𝑏 of bindings.
3. Substitute those bindings 𝑏 in e2, yielding a new expression e2'.
4. Evaluate e2' to a value v2.
5. The result of evaluating the let expression is v2.
Static semantics.
• If all the following hold then (let p = e1 in e2) : t2:
– e1 : t1
– the pattern variables in p are x1..xn
– e2 : t2 under the assumption that for all i in 1..n it holds that xi : ti,
Let definitions.
As before, a let definition can be understood as a let expression whose body has not yet been given. So their syntax can
be generalized to

let p = e

and their semantics follow from the semantics of let expressions, as before.

5.5.2 Pattern Matching with Functions

The syntax we’ve been using so far for functions is also a special case of the full syntax that OCaml permits. That syntax
is:

let f p1 ... pn = e1 in e2 (* function as part of let expression *)


let f p1 ... pn = e (* function definition at toplevel *)
fun p1 ... pn -> e (* anonymous function *)

5.5. Advanced Pattern Matching 97


OCaml Programming: Correct + Efficient + Beautiful

The truly primitive syntactic form we need to care about is fun p -> e. Let’s revisit the semantics of anonymous
functions and their application with that form; the changes to the other forms follow from those below:
Static semantics.
• Let x1..xn be the pattern variables appearing in p. If by assuming that x1 : t1 and x2 : t2 and … and
xn : tn, we can conclude that p : t and e :u, then fun p -> e : t -> u.
• The type checking rule for application is unchanged.
Dynamic semantics.
• The evaluation rule for anonymous functions is unchanged.
• To evaluate e0 e1:
1. Evaluate e0 to an anonymous function fun p -> e, and evaluate e1 to value v1.
2. Match v1 against pattern p. If it doesn’t match, raise the exception Match_failure. Otherwise, if it does
match, it produces a set 𝑏 of bindings.
3. Substitute those bindings 𝑏 in e, yielding a new expression e'.
4. Evaluate e' to a value v, which is the result of evaluating e0 e1.

5.5.3 Pattern Matching Examples

Here are several ways to get a Pokémon’s hit points:

(* Pokemon types *)
type ptype = TNormal | TFire | TWater

(* A record to represent Pokemon *)


type mon = { name : string; hp : int; ptype : ptype }

(* OK *)
let get_hp m = match m with { name = n; hp = h; ptype = t } -> h

(* better *)
let get_hp m = match m with { name = _; hp = h; ptype = _ } -> h

(* better *)
let get_hp m = match m with { name; hp; ptype } -> hp

(* better *)
let get_hp m = match m with { hp } -> hp

(* best *)
let get_hp m = m.hp

type ptype = TNormal | TFire | TWater

type mon = { name : string; hp : int; ptype : ptype; }

val get_hp : mon -> int = <fun>

98 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

val get_hp : mon -> int = <fun>

val get_hp : mon -> int = <fun>

val get_hp : mon -> int = <fun>

val get_hp : mon -> int = <fun>

Here’s how to get the first and second components of a pair:

let fst (x, _) = x

let snd (_, y) = y

val fst : 'a * 'b -> 'a = <fun>

val snd : 'a * 'b -> 'b = <fun>

Both fst and snd are actually already defined for you in the standard library.
Finally, here are several ways to get the 3rd component of a triple:

(* OK *)
let thrd t = match t with x, y, z -> z

(* good *)
let thrd t =
let x, y, z = t in
z

(* better *)
let thrd t =
let _, _, z = t in
z

(* best *)
let thrd (_, _, z) = z

val thrd : 'a * 'b * 'c -> 'c = <fun>

val thrd : 'a * 'b * 'c -> 'c = <fun>

val thrd : 'a * 'b * 'c -> 'c = <fun>

val thrd : 'a * 'b * 'c -> 'c = <fun>

The standard library does not define any functions for triples, quadruples, etc.

5.5. Advanced Pattern Matching 99


OCaml Programming: Correct + Efficient + Beautiful

5.6 Type Synonyms

A type synonym is a new name for an already existing type. For example, here are some type synonyms that might be
useful in representing some types from linear algebra:

type point = float * float


type vector = float list
type matrix = float list list

type point = float * float

type vector = float list

type matrix = float list list

Anywhere that a float * float is expected, you could use point, and vice-versa. The two are completely ex-
changeable for one another. In the following code, get_x doesn’t care whether you pass it a value that is annotated as
one vs. the other:

let get_x = fun (x, _) -> x

let p1 : point = (1., 2.)


let p2 : float * float = (1., 3.)

let a = get_x p1
let b = get_x p2

val get_x : 'a * 'b -> 'a = <fun>

val p1 : point = (1., 2.)

val p2 : float * float = (1., 3.)

val a : float = 1.

val b : float = 1.

Type synonyms are useful because they let us give descriptive names to complex types. They are a way of making code
more self-documenting.

100 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.7 Options

Suppose you want to write a function that usually returns a value of type t, but sometimes returns nothing. For example,
you might want to define a function list_max that returns the maximum value in a list, but there’s not a sensible thing
to return on an empty list:

let rec list_max = function


| [] -> ???
| h :: t -> max h (list_max t)

There are a couple possibilities to consider:


• Return min_int? But then list_max will only work for integers— not floats or other types.
• Raise an exception? But then the user of the function has to remember to catch the exception.
• Return null? That works in Java, but by design OCaml does not have a null value. That’s actually a good thing:
null pointer bugs are not fun to debug.

Note: Sir Tony Hoare calls his invention of null a “billion-dollar mistake”.

In addition to those possibilities, OCaml provides something even better called an option. (Haskellers will recognize
options as the Maybe monad.)
You can think of an option as being like a closed box. Maybe there’s something inside the box, or maybe box is empty.
We don’t know which until we open the box. If there turns out to be something inside the box when we open it, we can
take that thing out and use it. Thus, options provide a kind of “maybe type,” which ultimately is a kind of one-of type:
the box is in one of two states, full or empty.
In list_max above, we’d like to metaphorically return a box that’s empty if the list is empty, or a box that contains the
maximum element of the list if the list is non-empty.
Here’s how we create an option that is like a box with 42 inside it:

Some 42

- : int option = Some 42

And here’s how we create an option that is like an empty box:

None

- : 'a option = None

The Some means there’s something inside the box, and it’s 42. The None means there’s nothing inside the box.
Like list, we call option a type constructor: given a type, it produces a new type; but, it is not itself a type. So for
any type t, we can write t option as a type. But option all by itself cannot be used as a type. Values of type
t option might contain a value of type t, or they might contain nothing. None has type 'a option because it’s
unconstrained what the type is of the thing inside — as there isn’t anything inside.
You can access the contents of an option value e using pattern matching. Here’s a function that extracts an int from an
option, if there is one inside, and converts it to a string:

5.7. Options 101


OCaml Programming: Correct + Efficient + Beautiful

let extract o =
match o with
| Some i -> string_of_int i
| None -> "";;

val extract : int option -> string = <fun>

And here are a couple of example usages of that function:

extract (Some 42);;


extract None;;

- : string = "42"

- : string = ""

Here’s how we can write list_max with options:

let rec list_max = function


| [] -> None
| h :: t -> begin
match list_max t with
| None -> Some h
| Some m -> Some (max h m)
end

val list_max : 'a list -> 'a option = <fun>

Tip: The begin..end wrapping the nested pattern match above is not strictly required here but is not a bad habit, as it
will head off potential syntax errors in more complicated code. The keywords begin and end are equivalent to ( and
).

In Java, every object reference is implicitly an option. Either there is an object inside the reference, or there is nothing
there. That “nothing” is represented by the value null. Java does not force programmers to explicitly check for the
null case, which leads to null pointer exceptions. OCaml options force the programmer to include a branch in the pattern
match for None, thus guaranteeing that the programmer thinks about the right thing to do when there’s nothing there. So
we can think of options as a principled way of eliminating null from the language. Using options is usually considered
better coding practice than raising exceptions, because it forces the caller to do something sensible in the None case.
Syntax and semantics of options.
• t option is a type for every type t.
• None is a value of type 'a option.
• Some e is an expression of type t option if e : t. If e ==> v then Some e ==> Some v

102 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.8 Association Lists

A map is a data structure that maps keys to values. Maps are also known as dictionaries. One easy implementation of a
map is an association list, which is a list of pairs. Here, for example, is an association list that maps some shape names to
the number of sides they have:

let d = [("rectangle", 4); ("nonagon", 9); ("icosagon", 20)]

val d : (string * int) list =


[("rectangle", 4); ("nonagon", 9); ("icosagon", 20)]

Note that an association list isn’t so much a built-in data type in OCaml as a combination of two other types: lists and
pairs.
Here are two functions that implement insertion and lookup in an association list:

(** [insert k v lst] is an association list that binds key [k] to value [v]
and otherwise is the same as [lst] *)
let insert k v lst = (k, v) :: lst

(** [lookup k lst] is [Some v] if association list [lst] binds key [k] to
value [v]; and is [None] if [lst] does not bind [k]. *)
let rec lookup k = function
| [] -> None
| (k', v) :: t -> if k = k' then Some v else lookup k t

val insert : 'a -> 'b -> ('a * 'b) list -> ('a * 'b) list = <fun>

val lookup : 'a -> ('a * 'b) list -> 'b option = <fun>

The insert function simply adds a new map from a key to a value at the front of the list. It doesn’t bother to check
whether the key is already in the list. The lookup function looks through the list from left to right. So if there did
happen to be multiple maps for a given key in the list, only the most recently inserted one would be returned.
Insertion in an association list is therefore constant time, and lookup is linear time. Although there are certainly more
efficient implementations of dictionaries—and we’ll study some later in this course—association lists are a very easy and
useful implementation for small dictionaries that aren’t performance critical. The OCaml standard library has functions
for association lists in the List module; look for List.assoc and the functions below it in the documentation. What
we just wrote as lookup is actually already defined as List.assoc_opt. There is no pre-defined insert function
in the library because it’s so trivial just to cons a pair on.

5.9 Algebraic Data Types

Thus far, we have seen variants simply as enumerating a set of constant values, such as:

type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

type ptype = TNormal | TFire | TWater

type peff = ENormal | ENotVery | Esuper

But variants are far more powerful than this.

5.8. Association Lists 103


OCaml Programming: Correct + Efficient + Beautiful

5.9.1 Variants that Carry Data

As a running example, here is a variant type shape that does more than just enumerate values:

type point = float * float


type shape =
| Point of point
| Circle of point * float (* center and radius *)
| Rect of point * point (* lower-left and upper-right corners *)

type point = float * float

type shape = Point of point | Circle of point * float | Rect of point * point

This type, shape, represents a shape that is either a point, a circle, or a rectangle. A point is represented by a constructor
Point that carries some additional data, which is a value of type point. A circle is represented by a constructor
Circle that carries two pieces of data: one of type point and the other of type float. Those data represent the
center of the circle and its radius. A rectangle is represented by a constructor Rect that carries two more points.
Here are a couple functions that use the shape type:

let area = function


| Point _ -> 0.0
| Circle (_, r) -> Float.pi *. (r ** 2.0)
| Rect ((x1, y1), (x2, y2)) ->
let w = x2 -. x1 in
let h = y2 -. y1 in
w *. h

let center = function


| Point p -> p
| Circle (p, _) -> p
| Rect ((x1, y1), (x2, y2)) -> ((x2 +. x1) /. 2.0, (y2 +. y1) /. 2.0)

val area : shape -> float = <fun>

val center : shape -> point = <fun>

The shape variant type is the same as those we’ve seen before in that it is defined in terms of a collection of constructors.
What’s different than before is that those constructors carry additional data along with them. Every value of type shape
is formed from exactly one of those constructors. Sometimes we call the constructor a tag, because it tags the data it
carries as being from that particular constructor.
Variant types are sometimes called tagged unions. Every value of the type is from the set of values that is the union of all
values from the underlying types that the constructor carries. For example, with the shape type, every value is tagged
with either Point or Circle or Rect and carries a value from:
• the set of all point values, unioned with
• the set of all point * float values, unioned with
• the set of all point * point values.
Another name for these variant types is an algebraic data type. “Algebra” here refers to the fact that variant types contain
both sum and product types, as defined in the previous lecture. The sum types come from the fact that a value of a variant

104 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

is formed by one of the constructors. The product types come from that fact that a constructor can carry tuples or records,
whose values have a sub-value from each of their component types.
Using variants, we can express a type that represents the union of several other types, but in a type-safe way. Here, for
example, is a type that represents either a string or an int:

type string_or_int =
| String of string
| Int of int

type string_or_int = String of string | Int of int

If we wanted to, we could use this type to code up lists (e.g.) that contain either strings or ints:

type string_or_int_list = string_or_int list

let rec sum : string_or_int list -> int = function


| [] -> 0
| String s :: t -> int_of_string s + sum t
| Int i :: t -> i + sum t

let lst_sum = sum [String "1"; Int 2]

type string_or_int_list = string_or_int list

val sum : string_or_int list -> int = <fun>

val lst_sum : int = 3

Variants thus provide a type-safe way of doing something that might before have seemed impossible.
Variants also make it possible to discriminate which tag a value was constructed with, even if multiple constructors carry
the same type. For example:

type t = Left of int | Right of int


let x = Left 1
let double_right = function
| Left i -> i
| Right i -> 2 * i

type t = Left of int | Right of int

val x : t = Left 1

5.9. Algebraic Data Types 105


OCaml Programming: Correct + Efficient + Beautiful

val double_right : t -> int = <fun>

5.9.2 Syntax and Semantics

Syntax.
To define a variant type:

type t = C1 [of t1] | ... | Cn [of tn]

The square brackets above denote that of ti is optional. Every constructor may individually either carry no data or
carry data. We call constructors that carry no data constant; and those that carry data, non-constant.
To write an expression that is a variant:

C e

Or:

depending on whether the constructor name C is non-constant or constant.


Dynamic semantics.
• If e ==> v then C e ==> C v, assuming C is non-constant.
• C is already a value, assuming C is constant.
Static semantics.
• If t = ... | C | ... then C : t.
• If t = ... | C of t' | ... and if e : t' then C e : t.
Pattern matching.
We add the following new pattern form to the list of legal patterns:
• C p
And we extend the definition of when a pattern matches a value and produces a binding as follows:
• If p matches v and produces bindings 𝑏, then C p matches C v and produces bindings 𝑏.

5.9.3 Catch-all Cases

One thing to beware of when pattern matching against variants is what Real World OCaml calls “catch-all cases”. Here’s
a simple example of what can go wrong. Let’s suppose you write this variant and function:

type color = Blue | Red

(* a thousand lines of code in between *)

let string_of_color = function


| Blue -> "blue"
| _ -> "red"

106 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

type color = Blue | Red

val string_of_color : color -> string = <fun>

Seems fine, right? But then one day you realize there are more colors in the world. You need to represent green. So you
go back and add green to your variant:

type color = Blue | Red | Green

(* a thousand lines of code in between *)

let string_of_color = function


| Blue -> "blue"
| _ -> "red"

type color = Blue | Red | Green

val string_of_color : color -> string = <fun>

But because of the thousand lines of code in between, you forget that string_of_color needs updating. And now,
all the sudden, you are red-green color blind:

string_of_color Green

- : string = "red"

The problem is the catch-all case in the pattern match inside string_of_color: the final case that uses the wildcard
pattern to match anything. Such code is not robust against future changes to the variant type.
If, instead, you had originally coded the function as follows, life would be better:

let string_of_color = function


| Blue -> "blue"
| Red -> "red"

File "[9]", lines 1-3, characters 22-17:

1 | ......................function

2 | | Blue -> "blue"

3 | | Red -> "red"

Warning 8 [partial-match]: this pattern-matching is not exhaustive.

Here is an example of a case that is not matched:

5.9. Algebraic Data Types 107


OCaml Programming: Correct + Efficient + Beautiful

Green

val string_of_color : color -> string = <fun>

The OCaml type checker now alerts you that you haven’t yet updated string_of_color to account for the new
constructor.
The moral of the story is: catch-all cases lead to buggy code. Avoid using them.

5.9.4 Recursive Variants

Variant types may mention their own name inside their own body. For example, here is a variant type that could be used
to represent something similar to int list:

type intlist = Nil | Cons of int * intlist

let lst3 = Cons (3, Nil) (* similar to 3 :: [] or [3] *)


let lst123 = Cons(1, Cons(2, lst3)) (* similar to [1; 2; 3] *)

let rec sum (l : intlist) : int =


match l with
| Nil -> 0
| Cons (h, t) -> h + sum t

let rec length : intlist -> int = function


| Nil -> 0
| Cons (_, t) -> 1 + length t

let empty : intlist -> bool = function


| Nil -> true
| Cons _ -> false

type intlist = Nil | Cons of int * intlist

val lst3 : intlist = Cons (3, Nil)

val lst123 : intlist = Cons (1, Cons (2, Cons (3, Nil)))

val sum : intlist -> int = <fun>

val length : intlist -> int = <fun>

val empty : intlist -> bool = <fun>

Notice that in the definition of intlist, we define the Cons constructor to carry a value that contains an intlist.
This makes the type intlist be recursive: it is defined in terms of itself.
Types may be mutually recursive if you use the and keyword:

type node = {value : int; next : mylist}


and mylist = Nil | Node of node

108 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

type node = { value : int; next : mylist; }


and mylist = Nil | Node of node

Any such mutual recursion must involve at least one variant or record type that the recursion “goes through”. For example,
the following is not allowed:

type t = u and u = t

File "[12]", line 1, characters 0-10:


1 | type t = u and u = t
^^^^^^^^^^
Error: The definition of t contains a cycle:
u

But this is:

type t = U of u and u = T of t

type t = U of u
and u = T of t

Record types may also be recursive:

type node = {value : int; next : node}

type node = { value : int; next : node; }

But plain old type synonyms may not be:

type t = t * t

File "[15]", line 1, characters 0-14:


1 | type t = t * t
^^^^^^^^^^^^^^
Error: The type abbreviation t is cyclic

Although node is a legal type definition, there is no way to construct a value of that type because of the circularity
involved: to construct the very first node value in existence, you would already need a value of type node to exist.
Later, when we cover imperative features, we’ll see a similar idea used (but successfully) for mutable linked lists.

5.9.5 Parameterized Variants

Variant types may be parameterized on other types. For example, the intlist type above could be generalized to
provide lists (coded up ourselves) over any type:

type 'a mylist = Nil | Cons of 'a * 'a mylist

let lst3 = Cons (3, Nil) (* similar to [3] *)


let lst_hi = Cons ("hi", Nil) (* similar to ["hi"] *)

5.9. Algebraic Data Types 109


OCaml Programming: Correct + Efficient + Beautiful

type 'a mylist = Nil | Cons of 'a * 'a mylist

val lst3 : int mylist = Cons (3, Nil)

val lst_hi : string mylist = Cons ("hi", Nil)

Here, mylist is a type constructor but not a type: there is no way to write a value of type mylist. But we can write
value of type int mylist (e.g., lst3) and string mylist (e.g., lst_hi). Think of a type constructor as being
like a function, but one that maps types to types, rather than values to value.
Here are some functions over 'a mylist:

let rec length : 'a mylist -> int = function


| Nil -> 0
| Cons (_, t) -> 1 + length t

let empty : 'a mylist -> bool = function


| Nil -> true
| Cons _ -> false

val length : 'a mylist -> int = <fun>

val empty : 'a mylist -> bool = <fun>

Notice that the body of each function is unchanged from its previous definition for intlist. All that we changed was
the type annotation. And that could even be omitted safely:

let rec length = function


| Nil -> 0
| Cons (_, t) -> 1 + length t

let empty = function


| Nil -> true
| Cons _ -> false

val length : 'a mylist -> int = <fun>

val empty : 'a mylist -> bool = <fun>

The functions we just wrote are an example of a language feature called parametric polymorphism. The functions don’t
care what the 'a is in 'a mylist, hence they are perfectly happy to work on int mylist or string mylist or
any other (whatever) mylist. The word “polymorphism” is based on the Greek roots “poly” (many) and “morph”
(form). A value of type 'a mylist could have many forms, depending on the actual type 'a.
As soon, though, as you place a constraint on what the type 'a might be, you give up some polymorphism. For example,

let rec sum = function


| Nil -> 0
| Cons (h, t) -> h + sum t

110 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

val sum : int mylist -> int = <fun>

The fact that we use the ( + ) operator with the head of the list constrains that head element to be an int, hence all
elements must be int. That means sum must take in an int mylist, not any other kind of 'a mylist.
It is also possible to have multiple type parameters for a parameterized type, in which case parentheses are needed:

type ('a, 'b) pair = {first : 'a; second : 'b}


let x = {first = 2; second = "hello"}

type ('a, 'b) pair = { first : 'a; second : 'b; }

val x : (int, string) pair = {first = 2; second = "hello"}

5.9.6 Polymorphic Variants

Thus far, whenever you’ve wanted to define a variant type, you have had to give it a name, such as day, shape, or 'a
mylist:

type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

type shape =
| Point of point
| Circle of point * float
| Rect of point * point

type 'a mylist = Nil | Cons of 'a * 'a mylist

type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

type shape = Point of point | Circle of point * float | Rect of point * point

type 'a mylist = Nil | Cons of 'a * 'a mylist

Occasionally, you might need a variant type only for the return value of a single function. For example, here’s a function
f that can either return an int or ∞; you are forced to define a variant type to represent that result:

type fin_or_inf = Finite of int | Infinity

let f = function
| 0 -> Infinity
| 1 -> Finite 1
| n -> Finite (-n)

type fin_or_inf = Finite of int | Infinity

val f : int -> fin_or_inf = <fun>

The downside of this definition is that you were forced to define fin_or_inf even though it won’t be used throughout
much of your program.

5.9. Algebraic Data Types 111


OCaml Programming: Correct + Efficient + Beautiful

There’s another kind of variant in OCaml that supports this kind of programming: polymorphic variants. Polymorphic
variants are just like variants, except:
1. You don’t have to declare their type or constructors before using them.
2. There is no name for a polymorphic variant type. (So another name for this feature could have been “anonymous
variants”.)
3. The constructors of a polymorphic variant start with a backquote character.
Using polymorphic variants, we can rewrite f:

let f = function
| 0 -> `Infinity
| 1 -> `Finite 1
| n -> `Finite (-n)

val f : int -> [> `Finite of int | `Infinity ] = <fun>

This type says that f either returns `Finite n for some n : int or `Infinity. The square brackets do not
denote a list, but rather a set of possible constructors. The > sign means that any code that pattern matches against a value
of that type must at least handle the constructors `Finite and `Infinity, and possibly more. For example, we
could write:

match f 3 with
| `NegInfinity -> "negative infinity"
| `Finite n -> "finite"
| `Infinity -> "infinite"

- : string = "finite"

It’s perfectly fine for the pattern match to include constructors other than `Finite or `Infinity, because f is
guaranteed never to return any constructors other than those.
There are other, more compelling uses for polymorphic variants that we’ll see later in the course. They are particularly
useful in libraries. For now, we generally will steer you away from extensive use of polymorphic variants, because their
types can become difficult to manage.

5.9.7 Built-in Variants

OCaml’s built-in list data type is really a recursive, parameterized variant. It is defined as follows:

type 'a list = [] | ( :: ) of 'a * 'a list

So list is really just a type constructor, with (value) constructors [] (which we pronounce “nil”) and :: (which we
pronounce “cons”).
OCaml’s built-in option data type is also really a parameterized variant. It’s defined as follows:

type 'a option = None | Some of 'a

So option is really just a type constructor, with (value) constructors None and Some.
You can see both list and option defined in the core OCaml library.

112 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

5.10 Exceptions

OCaml has an exception mechanism similar to many other programming languages. A new type of OCaml exception is
defined with this syntax:

exception E of t

where E is a constructor name and t is a type. The of t is optional. Notice how this is similar to defining a constructor
of a variant type. For example:

exception A
exception B
exception Code of int
exception Details of string

exception A

exception B

exception Code of int

exception Details of string

To create an exception value, use the same syntax you would for creating a variant value. Here, for example, is an exception
value whose constructor is Failure, which carries a string:

Failure "something went wrong"

- : exn = Failure "something went wrong"

This constructor is pre-defined in the standard library and is one of the more common exceptions that OCaml programmers
use.
To raise an exception value e, simply write

raise e

There is a convenient function failwith : string -> 'a in the standard library that raises Failure. That is,
failwith s is equivalent to raise (Failure s).
To catch an exception, use this syntax:

try e with
| p1 -> e1
| ...
| pn -> en

The expression e is what might raise an exception. If it does not, the entire try expression evaluates to whatever e does.
If e does raise an exception value v, that value v is matched against the provided patterns, exactly like match expression.

5.10. Exceptions 113


OCaml Programming: Correct + Efficient + Beautiful

5.10.1 Exceptions are Extensible Variants

All exception values have type exn, which is a variant defined in the core. It’s an unusual kind of variant, though, called
an extensible variant, which allows new constructors of the variant to be defined after the variant type itself is defined. See
the OCaml manual for more information about extensible variants if you’re interested.

5.10.2 Exception Semantics

Since they are just variants, the syntax and semantics of exceptions is already covered by the syntax and semantics of
variants—with one exception (pun intended), which is the dynamic semantics of how exceptions are raised and handled.
Dynamic semantics. As we originally said, every OCaml expression either
• evaluates to a value
• raises an exception
• or fails to terminate (i.e., an “infinite loop”).
So far we’ve only presented the part of the dynamic semantics that handles the first of those three cases. What happens
when we add exceptions? Now, evaluation of an expression either produces a value or produces an exception packet.
Packets are not normal OCaml values; the only pieces of the language that recognizes them are raise and try. The
exception value produced by (e.g.) Failure "oops" is part of the exception packet produced by raise (Failure
"oops"), but the packet contains more than just the exception value; there can also be a stack trace, for example.
For any expression e other than try, if evaluation of a subexpression of e produces an exception packet P, then evaluation
of e produces packet P.
But now we run into a problem for the first time: what order are subexpressions evaluated in? Sometimes the answer to
that question is provided by the semantics we have already developed. For example, with let expressions, we know that
the binding expression must be evaluated before the body expression. So the following code raises A:

let _ = raise A in raise B;;

Exception: A.
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

And with functions, OCaml does not officially specify the evaluation order of a function and its argument, but the current
implementation evaluates the argument before the function. So the following code also raises A, in addition to producing
some compiler warnings that the first expression will never actually be applied as a function to an argument:

(raise B) (raise A)

It makes sense that both those pieces of code would raise the same exception, given that we know let x = e1 in
e2 is syntactic sugar for (fun x -> e2) e1.
But what does the following code raise as an exception?

(raise A, raise B)

The answer is nuanced. The language specification does not stipulate what order the components of pairs should be
evaluated in. Nor did our semantics exactly determine the order. (Though you would be forgiven if you thought it was
left to right.) So programmers actually cannot rely on that order. The current implementation of OCaml, as it turns out,

114 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

evaluates right to left. So the code above actually raises B. If you really want to force the evaluation order, you need to
use let expressions:

let a = raise A in
let b = raise B in
(a, b)

Exception: A.
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

That code is guaranteed to raise A rather than B.


One interesting corner case is what happens when a raise expression itself has a subexpression that raises:

exception C of string;;
exception D of string;;
raise (C (raise (D "oops")))

exception C of string

exception D of string

Exception: D "oops".
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89,␣
↪characters 4-150

That code ends up raising D, because the first thing that has to happen is to evaluate C (raise (D "oops"))
to a value. Doing that requires evaluating raise (D "oops") to a value. Doing that causes a packet containing
D "oops" to be produced, and that packet then propagates and becomes the result of evaluating C (raise (D
"oops")), hence the result of evaluating raise (C (raise (D "oops"))).
Once evaluation of an expression produces an exception packet P, that packet propagates until it reaches a try expression:

try e with
| p1 -> e1
| ...
| pn -> en

The exception value inside P is matched against the provided patterns using the usual evaluation rules for pattern
matching—with one exception (again, pun intended). If none of the patterns matches, then instead of producing
Match_failure inside a new exception packet, the original exception packet P continues propagating until the next
try expression is reached.

5.10. Exceptions 115


OCaml Programming: Correct + Efficient + Beautiful

5.10.3 Pattern Matching

There is a pattern form for exceptions. Here’s an example of its usage:

match List.hd [] with


| [] -> "empty"
| _ :: _ -> "non-empty"
| exception (Failure s) -> s

- : string = "hd"

Note that the code above is just a standard match expression, not a try expression. It matches the value of List.
hd [] against the three provided patterns. As we know, List.hd [] will raise an exception containing the value
Failure "hd". The exception pattern exception (Failure s) matches that value. So the above code will
evaluate to "hd".
Exception patterns are a kind of syntactic sugar. Consider this code for example:

match e with
| p1 -> e1
| exception p2 -> e2
| p3 -> e3
| exception p4 -> e4

We can rewrite the code to eliminate the exception pattern:

try
match e with
| p1 -> e1
| p3 -> e3
with
| p2 -> e2
| p4 -> e4

In general if there are both exception and non-exception patterns, evaluation proceeds as follows: try evaluating e. If it
produces an exception packet, use the exception patterns from the original match expression to handle that packet. If it
doesn’t produce an exception packet but instead produces a non-exception value, use the non-exception patterns from the
original match expression to match that value.

5.10.4 Exceptions and OUnit

If it is part of a function’s specification that it raises an exception, you might want to write OUnit tests that check whether
the function correctly does so. Here’s how to do that:

open OUnit2

let tests = "suite" >::: [


"empty" >:: (fun _ -> assert_raises (Failure "hd") (fun () -> List.hd []));
]

let _ = run_test_tt_main tests

The expression assert_raises exn (fun () -> e) checks to see whether expression e raises exception exn.
If so, the OUnit test case succeeds, otherwise it fails.

116 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

Note that the second argument of assert_raises is a function of type unit -> 'a, sometimes called a “thunk”.
It may seem strange to write a function with this type—the only possible input is ()—but this is a common pattern in
functional languages to suspend or delay the evaluation of a program. In this case, we want assert_raises to evaluate
List.hd [] when it is ready. If we evaluated List.hd [] immediately, assert_raises would not be able to
check if the right exception is raised. We’ll learn more about thunks in a later chapter.

Warning: A common error is to forget the (fun () -> ...) around e. If you make this mistake, the program
may still typecheck but the OUnit test case will fail: without the extra anonymous function, the exception is raised
before assert_raises ever gets a chance to handle it.

5.11 Example: Trees

Trees are a very useful data structure. A binary tree, as you’ll recall from CS 2110, is a node containing a value and two
children that are trees. A binary tree can also be an empty tree, which we also use to represent the absence of a child
node.

5.11.1 Representation with Tuples

Here is a definition for a binary tree data type:

type 'a tree =


| Leaf
| Node of 'a * 'a tree * 'a tree

type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree

A node carries a data item of type 'a and has a left and right subtree. A leaf is empty. Compare this definition to the
definition of a list and notice how similar their structure is:

type 'a tree = type 'a mylist =


| Leaf | Nil
| Node of 'a * 'a tree * 'a tree | Cons of 'a * 'a mylist

The only essential difference is that Cons carries one sublist, whereas Node carries two subtrees.
Here is code that constructs a small tree:

(* the code below constructs this tree:


4
/ \
2 5
/ \ / \
1 3 6 7
*)
let t =
Node(4,
Node(2,
Node(1, Leaf, Leaf),
Node(3, Leaf, Leaf)
),
(continues on next page)

5.11. Example: Trees 117


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


Node(5,
Node(6, Leaf, Leaf),
Node(7, Leaf, Leaf)
)
)

val t : int tree =


Node (4, Node (2, Node (1, Leaf, Leaf), Node (3, Leaf, Leaf)),
Node (5, Node (6, Leaf, Leaf), Node (7, Leaf, Leaf)))

The size of a tree is the number of nodes in it (that is, Nodes, not Leafs). For example, the size of tree t above is 7.
Here is a function size : 'a tree -> int that returns the number of nodes in a tree:

let rec size = function


| Leaf -> 0
| Node (_, l, r) -> 1 + size l + size r

5.11.2 Representation with Records

Next, let’s revise our tree type to use a record type to represent a tree node. In OCaml we have to define two mutually
recursive types, one to represent a tree node, and one to represent a (possibly empty) tree:

type 'a tree =


| Leaf
| Node of 'a node

and 'a node = {


value: 'a;
left: 'a tree;
right: 'a tree
}

type 'a tree = Leaf | Node of 'a node


and 'a node = { value : 'a; left : 'a tree; right : 'a tree; }

Here’s an example tree:

(* represents
2
/ \
1 3 *)
let t =
Node {
value = 2;
left = Node {value = 1; left = Leaf; right = Leaf};
right = Node {value = 3; left = Leaf; right = Leaf}
}

val t : int tree =


Node
(continues on next page)

118 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


{value = 2; left = Node {value = 1; left = Leaf; right = Leaf};
right = Node {value = 3; left = Leaf; right = Leaf}}

We can use pattern matching to write the usual algorithms for recursively traversing trees. For example, here is a recursive
search over the tree:

(** [mem x t] is whether [x] is a value at some node in tree [t]. *)


let rec mem x = function
| Leaf -> false
| Node {value; left; right} -> value = x || mem x left || mem x right

val mem : 'a -> 'a tree -> bool = <fun>

The function name mem is short for “member”; the standard library often uses a function of this name to implement a
search through a collection data structure to determine whether some element is a member of that collection.
Here’s a function that computes the preorder traversal of a tree, in which each node is visited before any of its children,
by constructing a list in which the values occur in the order in which they would be visited:

let rec preorder = function


| Leaf -> []
| Node {value; left; right} -> [value] @ preorder left @ preorder right

val preorder : 'a tree -> 'a list = <fun>

preorder t

- : int list = [2; 1; 3]

Although the algorithm is beautifully clear from the code above, it takes quadratic time on unbalanced trees because of
the @ operator. That problem can be solved by introducing an extra argument acc to accumulate the values at each node,
though at the expense of making the code less clear:

let preorder_lin t =
let rec pre_acc acc = function
| Leaf -> acc
| Node {value; left; right} -> value :: (pre_acc (pre_acc acc right) left)
in pre_acc [] t

val preorder_lin : 'a tree -> 'a list = <fun>

The version above uses exactly one :: operation per Node in the tree, making it linear time.

5.11. Example: Trees 119


OCaml Programming: Correct + Efficient + Beautiful

5.12 Example: Natural Numbers

We can define a recursive variant that acts like numbers, demonstrating that we don’t really have to have numbers built
into OCaml! (For sake of efficiency, though, it’s a good thing they are.)
A natural number is either zero or the successor of some other natural number. This is how you might define the natural
numbers in a mathematical logic course, and it leads naturally to the following OCaml type nat:

type nat = Zero | Succ of nat

type nat = Zero | Succ of nat

We have defined a new type nat, and Zero and Succ are constructors for values of this type. This allows us to build
expressions that have an arbitrary number of nested Succ constructors. Such values act like natural numbers:

let zero = Zero


let one = Succ zero
let two = Succ one
let three = Succ two
let four = Succ three

val zero : nat = Zero

val one : nat = Succ Zero

val two : nat = Succ (Succ Zero)

val three : nat = Succ (Succ (Succ Zero))

val four : nat = Succ (Succ (Succ (Succ Zero)))

Now we can write functions to manipulate values of this type. We’ll write a lot of type annotations in the code below to
help the reader keep track of which values are nat versus int; the compiler, of course, doesn’t need our help.

let iszero = function


| Zero -> true
| Succ _ -> false

let pred = function


| Zero -> failwith "pred Zero is undefined"
| Succ m -> m

val iszero : nat -> bool = <fun>

val pred : nat -> nat = <fun>

Similarly, we can define a function to add two numbers:

120 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

let rec add n1 n2 =


match n1 with
| Zero -> n2
| Succ pred_n -> add pred_n (Succ n2)

val add : nat -> nat -> nat = <fun>

We can convert nat values to type int and vice-versa:

let rec int_of_nat = function


| Zero -> 0
| Succ m -> 1 + int_of_nat m

let rec nat_of_int = function


| i when i = 0 -> Zero
| i when i > 0 -> Succ (nat_of_int (i - 1))
| _ -> failwith "nat_of_int is undefined on negative ints"

val int_of_nat : nat -> int = <fun>

val nat_of_int : int -> nat = <fun>

To determine whether a natural number is even or odd, we can write a pair of mutually recursive functions:

let rec even = function Zero -> true | Succ m -> odd m
and odd = function Zero -> false | Succ m -> even m

val even : nat -> bool = <fun>


val odd : nat -> bool = <fun>

5.13 Summary

Lists are a highly useful built-in data structure in OCaml. The language provides a lightweight syntax for building them,
rather than requiring you to use a library. Accessing parts of a list makes use of pattern matching, a very powerful feature
(as you might expect from its rather lengthy semantics). We’ll see more uses for pattern matching as the course proceeds.
These built-in lists are implemented as singly-linked lists. That’s important to keep in mind when your needs go beyond
small- to medium-sized lists. Recursive functions on long lists will take up a lot of stack space, so tail recursion becomes
important. And if you’re attempting to process really huge lists, you probably don’t want linked lists at all, but instead a
data structure that will do a better job of exploiting memory locality.
OCaml provides data types for variants (one-of types), tuples and products (each-of types), and options (maybe types).
Pattern matching can be used to access values of each of those data types. And pattern matching can be used in let
expressions and functions.
Association lists combine lists and tuples to create a lightweight implementation of dictionaries.
Variants are a powerful language feature. They are the workhorse of representing data in a functional language. OCaml
variants actually combine several theoretically independent language features into one: sum types, product types, recursive
types, and parameterized (polymorphic) types. The result is an ability to express many kinds of data, including lists,
options, trees, and even exceptions.

5.13. Summary 121


OCaml Programming: Correct + Efficient + Beautiful

5.13.1 Terms and Concepts

• algebraic data type


• append
• association list
• binary trees as variants
• binding
• branch
• carried data
• catch-all cases
• cons
• constant constructor
• constructor
• copying
• desugaring
• each-of type
• exception
• exception as variants
• exception packet
• exception pattern
• exception value
• exhaustiveness
• field
• head
• induction
• leaf
• list
• lists as variants
• maybe type
• mutually recursive functions
• natural numbers as variants
• nil
• node
• non-constant constructor
• one-of type
• options
• options as variants

122 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

• order of evaluation
• pair
• parameterized variant
• parametric polymorphism
• pattern matching
• prepend
• product type
• record
• recursion
• recursive variant
• sharing
• stack frame
• sum type
• syntactic sugar
• tag
• tail
• tail call
• tail recursion
• test-driven development (TDD)
• triple
• tuple
• type constructor
• type synonym
• variant
• wildcard

5.13.2 Further Reading

• Introduction to Objective Caml, chapters 4, 5.2, 5.3, 5.4, 6, 7, 8.1


• OCaml from the Very Beginning, chapters 3, 4, 5, 7, 8, 10, 11
• Real World OCaml, chapter 3, 5, 6, 7

5.13. Summary 123


OCaml Programming: Correct + Efficient + Beautiful

5.14 Exercises

Solutions to most exercises are available. Fall 2022 is the first public release of these solutions. Though they have been
available to Cornell students for a few years, it is inevitable that wider circulation will reveal improvements that could be
made. We are happy to add or correct solutions. Please make contributions through GitHub.

Exercise: list expressions [★]


• Construct a list that has the integers 1 through 5 in it. Use the square bracket notation for lists.
• Construct the same list, but do not use the square bracket notation. Instead, use :: and [].
• Construct the same list again. This time, the following expression must appear in your answer: [2; 3; 4]. Use
the @ operator, and do not use ::.

Exercise: product [★★]


Write a function product that returns the product of all the elements in a list. The product of all the elements of an
empty list is 1.

Exercise: concat [★★]


Write a function that concatenates all the strings in a list. The concatenation of all the strings in an empty list is the empty
string "".

Exercise: product test [★★]


Unit test the function product that you wrote in an exercise above.

Exercise: patterns [★★★]


Using pattern matching, write three functions, one for each of the following properties. Your functions should return
true if the input list has the property and false otherwise.
• the list’s first element is "bigred"
• the list has exactly two or four elements; do not use the length function
• the first two elements of the list are equal

Exercise: library [★★★]


Consult the List standard library to solve these exercises:
• Write a function that takes an int list and returns the fifth element of that list, if such an element exists. If the
list has fewer than five elements, return 0. Hint: List.length and List.nth.
• Write a function that takes an int list and returns the list sorted in descending order. Hint: List.sort with
Stdlib.compare as its first argument, and List.rev.

124 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

Exercise: library test [★★★]


Write a couple OUnit unit tests for each of the functions you wrote in the previous exercise.

Exercise: library puzzle [★★★]


• Write a function that returns the last element of a list. Your function may assume that the list is non-empty. Hint:
Use two library functions, and do not write any pattern matching code of your own.
• Write a function any_zeros : int list -> bool that returns true if and only if the input list contains
at least one 0. Hint: use one library function, and do not write any pattern matching code of your own.
Your solutions will be only one or two lines of code each.

Exercise: take drop [★★★]


• Write a function take : int -> 'a list -> 'a list such that take n lst returns the first n
elements of lst. If lst has fewer than n elements, return all of them.
• Write a function drop : int -> 'a list -> 'a list such that drop n lst returns all but the first
n elements of lst. If lst has fewer than n elements, return the empty list.

Exercise: take drop tail [★★★★]


Revise your solutions for take and drop to be tail recursive, if they aren’t already. Test them on long lists with large
values of n to see whether they run out of stack space. To construct long lists, use the -- operator from the lists section.

Exercise: unimodal [★★★]


Write a function is_unimodal : int list -> bool that takes an integer list and returns whether that list is
unimodal. A unimodal list is a list that monotonically increases to some maximum value then monotonically decreases
after that value. Either or both segments (increasing or decreasing) may be empty. A constant list is unimodal, as is the
empty list.

Exercise: powerset [★★★]


Write a function powerset : int list -> int list list that takes a set S represented as a list and returns
the set of all subsets of S. The order of subsets in the powerset and the order of elements in the subsets do not matter.
Hint: Consider the recursive structure of this problem. Suppose you already have p, such that p = powerset s. How
could you use p to compute powerset (x :: s)?

Exercise: print int list rec [★★]


Write a function print_int_list : int list -> unit that prints its input list, one number per line. For
example, print_int_list [1; 2; 3] should result in this output:

1
2
3

5.14. Exercises 125


OCaml Programming: Correct + Efficient + Beautiful

Here is some code to get you started:

let rec print_int_list = function


| [] -> ()
| h :: t -> (* fill in here *); print_int_list t

Exercise: print int list iter [★★]


Write a function print_int_list' : int list -> unit whose specification is the same as
print_int_list. Do not use the keyword rec in your solution, but instead to use the List module function List.
iter. Here is some code to get you started:

let print_int_list' lst =


List.iter (fun x -> (* fill in here *)) lst

Exercise: student [★★]


Assume the following type definition:

type student = {first_name : string; last_name : string; gpa : float}

Give OCaml expressions that have the following types:


• student
• student -> string * string (a function that extracts the student’s name)
• string -> string -> float -> student (a function that creates a student record)

Exercise: pokerecord [★★]


Here is a variant that represents a few Pokémon types:

type poketype = Normal | Fire | Water

• Define the type pokemon to be a record with fields name (a string), hp (an integer), and ptype (a poketype).
• Create a record named charizard of type pokemon that represents a Pokémon with 78 HP and Fire type.
• Create a record named squirtle of type pokemon that represents a Pokémon with 44 HP and Water type.

Exercise: safe hd and tl [★★]


Write a function safe_hd : 'a list -> 'a option that returns Some x if the head of the input list is x,
and None if the input list is empty.
Also write a function safe_tl : 'a list -> 'a list option that returns the tail of the list, or None if
the list is empty.

Exercise: pokefun [★★★]


Write a function max_hp : pokemon list -> pokemon option that, given a list of pokemon, finds the
Pokémon with the highest HP.

126 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

Exercise: date before [★★]


Define a date-like triple to be a value of type int * int * int. Examples of date-like triples include (2013, 2,
1) and (0, 0, 1000). A date is a date-like triple whose first part is a positive year (i.e., a year in the common era),
second part is a month between 1 and 12, and third part is a day between 1 and 31 (or 30, 29, or 28, depending on the
month and year). (2013, 2, 1) is a date; (0, 0, 1000) is not.
Write a function is_before that takes two dates as input and evaluates to true or false. It evaluates to true if
the first argument is a date that comes before the second argument. (If the two dates are the same, the result is false.)
Your function needs to work correctly only for dates, not for arbitrary date-like triples. However, you will probably find
it easier to write your solution if you think about making it work for arbitrary date-like triples. For example, it’s easier
to forget about whether the input is truly a date, and simply write a function that claims (for example) that January 100,
2013 comes before February 34, 2013—because any date in January comes before any date in February, but a function
that says that January 100, 2013 comes after February 34, 2013 is also valid. You may ignore leap years.

Exercise: earliest date [★★★]


Write a function earliest : (int*int*int) list -> (int * int * int) option. It evaluates to
None if the input list is empty, and to Some d if date d is the earliest date in the list. Hint: use is_before.
As in the previous exercise, your function needs to work correctly only for dates, not for arbitrary date-like triples.

Exercise: assoc list [★]


Use the functions insert and lookup from the section on association lists to construct an association list that maps the
integer 1 to the string “one”, 2 to “two”, and 3 to “three”. Lookup the key 2. Lookup the key 4.

Exercise: cards [★★]


• Define a variant type suit that represents the four suits, ♣ ♦ ♥ ♠, in a standard 52-card deck. All the constructors
of your type should be constant.
• Define a type rank that represents the possible ranks of a card: 2, 3, …, 10, Jack, Queen, King, or Ace. There
are many possible solutions; you are free to choose whatever works for you. One is to make rank be a synonym
of int, and to assume that Jack=11, Queen=12, King=13, and Ace=1 or 14. Another is to use variants.
• Define a type card that represents the suit and rank of a single card. Make it a record with two fields.
• Define a few values of type card: the Ace of Clubs, the Queen of Hearts, the Two of Diamonds, the Seven of
Spades.

Exercise: matching [★]


For each pattern in the list below, give a value of type int option list that does not match the pattern and is not
the empty list, or explain why that’s impossible.
• Some x :: tl
• [Some 3110; None]
• [Some x; _]
• h1 :: h2 :: tl

5.14. Exercises 127


OCaml Programming: Correct + Efficient + Beautiful

• h :: tl

Exercise: quadrant [★★]

Complete the quadrant function below, which should return the quadrant of the given x, y point according to the
diagram on the right (borrowed from Wikipedia). Points that lie on an axis do not belong to any quadrant. Hints: (a)
define a helper function for the sign of an integer, (b) match against a pair.

type quad = I | II | III | IV


type sign = Neg | Zero | Pos

let sign (x:int) : sign =


...

let quadrant : int*int -> quad option = fun (x,y) ->


match ... with
| ... -> Some I
| ... -> Some II
| ... -> Some III
| ... -> Some IV
| ... -> None

Exercise: quadrant when [★★]


Rewrite the quadrant function to use the when syntax. You won’t need your helper function from before.

let quadrant_when : int*int -> quad option = function


| ... when ... -> Some I
| ... when ... -> Some II
| ... when ... -> Some III
| ... when ... -> Some IV
| ... -> None

Exercise: depth [★★]


Write a function depth : 'a tree -> int that returns the number of nodes in any longest path from the root to
a leaf. For example, the depth of an empty tree (simply Leaf) is 0, and the depth of tree t above is 3. Hint: there is a
library function max : 'a -> 'a -> 'a that returns the maximum of any two values of the same type.

Exercise: shape [★★★]


Write a function same_shape : 'a tree -> 'b tree -> bool that determines whether two trees have the
same shape, regardless of whether the values they carry at each node are the same. Hint: use a pattern match with three
branches, where the expression being matched is a pair of trees.

Exercise: list max exn [★★]


Write a function list_max : int list -> int that returns the maximum integer in a list, or raises Failure
"list_max" if the list is empty.

128 Chapter 5. Data and Types


OCaml Programming: Correct + Efficient + Beautiful

Exercise: list max exn string [★★]


Write a function list_max_string : int list -> string that returns a string containing the maximum
integer in a list, or the string "empty" (note, not the exception Failure "empty" but just the string "empty") if
the list is empty. Hint: string_of_int in the standard library will do what its name suggests.

Exercise: list max exn ounit [★]


Write two OUnit tests to determine whether your solution to list max exn, above, correctly raises an exception when its
input is the empty list, and whether it correctly returns the max value of the input list when that list is non-empty.

Exercise: is_bst [★★★★]


Write a function is_bst : ('a*'b) tree -> bool that returns true if and only if the given tree satisfies the
binary search tree invariant. An efficient version of this function that visits each node at most once is somewhat tricky to
write. Hint: write a recursive helper function that takes a tree and either gives you (i) the minimum and maximum value
in the tree, or (ii) tells you that the tree is empty, or (iii) tells you that the tree does not satisfy the invariant. Your is_bst
function will not be recursive, but will call your helper function and pattern match on the result. You will need to define a
new variant type for the return type of your helper function.

Exercise: quadrant poly [★★]


Modify your definition of quadrant to use polymorphic variants. The types of your functions should become these:

val sign : int -> [> `Neg | `Pos | `Zero ]


val quadrant : int * int -> [> `I | `II | `III | `IV ] option

5.14. Exercises 129


OCaml Programming: Correct + Efficient + Beautiful

130 Chapter 5. Data and Types


CHAPTER

SIX

HIGHER-ORDER PROGRAMMING

Functions are values just like any other value in OCaml. What does that mean exactly? This means that we can pass
functions around as arguments to other functions, that we can store functions in data structures, that we can return functions
as a result from other functions, and so forth.
Higher-order functions either take other functions as input or return other functions as output (or both). Higher-order
functions are also known as functionals, and programming with them could therefore be called functional programming—
indicating what the heart of programming in languages like OCaml is all about.
Higher-order functions were one of the more recent adoptions from functional languages into mainstream languages. The
Java 8 Streams library and Python 2.3’s itertools modules are examples of that; C++ has also been increasing its
support since at least 2011.

Note: C wizards might object the adoption isn’t so recent. After all, C has long had the ability to do higher-order
programming through function pointers. But that ability also depends on the programming pattern of passing an additional
environment parameter to provide the values of variables in the function to be called through the pointer. As we’ll see in
our later chapter on interpreters, the essence of (higher-order) functions in a functional language is that they are really
something called a closure that obviates the need for that extra parameter. Bear in mind that the issue is not what is
possible to compute in a language—after all everything is eventually compiled down to machine code, so we could just
write in that exclusively—but what is pleasant to compute.

In this chapter we will see what all the fuss is about. Higher-order functions enable beautiful, general, reusable code.

6.1 Higher-Order Functions

Consider these functions double and square on integers:

let double x = 2 * x
let square x = x * x

val double : int -> int = <fun>

val square : int -> int = <fun>

Let’s use these functions to write other functions that quadruple and raise a number to the fourth power:

let quad x = double (double x)


let fourth x = square (square x)

131
OCaml Programming: Correct + Efficient + Beautiful

val quad : int -> int = <fun>

val fourth : int -> int = <fun>

There is an obvious similarity between these two functions: what they do is apply a given function twice to a value. By
passing in the function to another function twice as an argument, we can abstract this functionality:

let twice f x = f (f x)

val twice : ('a -> 'a) -> 'a -> 'a = <fun>

The function twice is higher-order: its input f is a function. And—recalling that all OCaml functions really take only a
single argument—its output is technically fun x -> f (f x), so twice returns a function hence is also higher-order
in that way.
Using twice, we can implement quad and fourth in a uniform way:

let quad x = twice double x


let fourth x = twice square x

val quad : int -> int = <fun>

val fourth : int -> int = <fun>

6.1.1 The Abstraction Principle

Above, we have exploited the structural similarity between quad and fourth to save work. Admittedly, in this toy
example it might not seem like much work. But imagine that twice were actually some much more complicated function.
Then, if someone comes up with a more efficient version of it, every function written in terms of it (like quad and
fourth) could benefit from that improvement in efficiency, without needing to be recoded.
Part of being an excellent programmer is recognizing such similarities and abstracting them by creating functions (or other
units of code) that implement them. Bruce MacLennan names this the Abstraction Principle in his textbook Functional
Programming: Theory and Practice (1990). The Abstraction Principle says to avoid requiring something to be stated more
than once; instead, factor out the recurring pattern. Higher-order functions enable such refactoring, because they allow
us to factor out functions and parameterize functions on other functions.
Besides twice, here are some more relatively simple examples, indebted also to MacLennan:
Apply. We can write a function that applies its first input to its second input:

let apply f x = f x

val apply : ('a -> 'b) -> 'a -> 'b = <fun>

Of course, writing apply f is a lot more work than just writing f.


Pipeline. The pipeline operator, which we’ve previously seen, is a higher-order function:

132 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

let pipeline x f = f x
let (|>) = pipeline
let x = 5 |> double

val pipeline : 'a -> ('a -> 'b) -> 'b = <fun>

val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun>

val x : int = 10

Compose. We can write a function that composes two other functions:

let compose f g x = f (g x)

val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>

This function would let us create a new function that can be applied many times, such as the following:

let square_then_double = compose double square


let x = square_then_double 1
let y = square_then_double 2

val square_then_double : int -> int = <fun>

val x : int = 2

val y : int = 8

Both. We can write a function that applies two functions to the same argument and returns a pair of the result:

let both f g x = (f x, g x)
let ds = both double square
let p = ds 3

val both : ('a -> 'b) -> ('a -> 'c) -> 'a -> 'b * 'c = <fun>

val ds : int -> int * int = <fun>

val p : int * int = (6, 9)

Cond. We can write a function that conditionally chooses which of two functions to apply based on a predicate:

let cond p f g x =
if p x then f x else g x

val cond : ('a -> bool) -> ('a -> 'b) -> ('a -> 'b) -> 'a -> 'b = <fun>

6.1. Higher-Order Functions 133


OCaml Programming: Correct + Efficient + Beautiful

6.1.2 The Meaning of “Higher Order”

The phrase “higher order” is used throughout logic and computer science, though not necessarily with a precise or con-
sistent meaning in all cases.
In logic, first-order quantification refers primarily to the universal and existential (∀ and ∃) quantifiers. These let you
quantify over some domain of interest, such as the natural numbers. But for any given quantification, say ∀𝑥, the variable
being quantified represents an individual element of that domain, say the natural number 42.
Second-order quantification lets you do something strictly more powerful, which is to quantify over properties of the
domain. Properties are assertions about individual elements, for example, that a natural number is even, or that it is prime.
In some logics we can equate properties with sets of individual, for example the set of all even naturals. So second-order
quantification is often thought of as quantification over sets. You can also think of properties as being functions that take
in an element and return a Boolean indicating whether the element satisfies the property; this is called the characteristic
function of the property.
Third-order logic would allow quantification over properties of properties, and fourth-order over properties of properties
of properties, and so forth. Higher-order logic refers to all these logics that are more powerful than first-order logic; though
one interesting result in this area is that all higher-order logics can be expressed in second-order logic.
In programming languages, first-order functions similarly refer to functions that operate on individual data elements (e.g.,
strings, ints, records, variants, etc.). Whereas higher-order function can operate on functions, much like higher-order
logics can quantify over properties (which are like functions).

6.1.3 Famous Higher-order Functions

In the next few sections we’ll dive into three of the most famous higher-order functions: map, filter, and fold. These are
functions that can be defined for many data structures, including lists and trees. The basic idea of each is that:
• map transforms elements,
• filter eliminates elements, and
• fold combines elements.

6.2 Map

Here are two functions we might want to write:

(** [add1 lst] adds 1 to each element of [lst]. *)


let rec add1 = function
| [] -> []
| h :: t -> (h + 1) :: add1 t

let lst1 = add1 [1; 2; 3]

val add1 : int list -> int list = <fun>

val lst1 : int list = [2; 3; 4]

(** [concat_bang lst] concatenates "!" to each element of [lst]. *)


let rec concat_bang = function
| [] -> []
(continues on next page)

134 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


| h :: t -> (h ^ "!") :: concat_bang t

let lst2 = concat_bang ["sweet"; "salty"]

val concat_bang : string list -> string list = <fun>

val lst2 : string list = ["sweet!"; "salty!"]

There’s a lot of similarity between those two functions:


• They both pattern match against a list.
• They both return the same value for the base case of the empty list.
• They both recurse on the tail in the case of a non-empty list.
In fact the only difference (other than their names) is what they do for the head element: add versus concatenate. Let’s
rewrite the two functions to make that difference even more explicit:

(** [add1 lst] adds 1 to each element of [lst]. *)


let rec add1 = function
| [] -> []
| h :: t ->
let f = fun x -> x + 1 in
f h :: add1 t

(** [concat_bang lst] concatenates "!" to each element of [lst]. *)


let rec concat_bang = function
| [] -> []
| h :: t ->
let f = fun x -> x ^ "!" in
f h :: concat_bang t

val add1 : int list -> int list = <fun>

val concat_bang : string list -> string list = <fun>

Now the only difference between the two functions (again, other than their names) is the body of helper function f. Why
repeat all that code when there’s such a small difference between the functions? We might as well abstract that one helper
function out from each main function and make it an argument:

let rec add1' f = function


| [] -> []
| h :: t -> f h :: add1' f t

(** [add1 lst] adds 1 to each element of [lst]. *)


let add1 = add1' (fun x -> x + 1)

let rec concat_bang' f = function


| [] -> []
| h :: t -> f h :: concat_bang' f t

(** [concat_bang lst] concatenates "!" to each element of [lst]. *)


let concat_bang = concat_bang' (fun x -> x ^ "!")

6.2. Map 135


OCaml Programming: Correct + Efficient + Beautiful

val add1' : ('a -> 'b) -> 'a list -> 'b list = <fun>

val add1 : int list -> int list = <fun>

val concat_bang' : ('a -> 'b) -> 'a list -> 'b list = <fun>

val concat_bang : string list -> string list = <fun>

But now there really is no difference at all between add1' and concat_bang' except for their names. They are totally
duplicated code. Even their types are now the same, because nothing about them mentions integers or strings. We might
as well just keep only one of them and come up with a good new name for it. One possibility could be transform,
because they transform a list by applying a function to each element of the list:

let rec transform f = function


| [] -> []
| h :: t -> f h :: transform f t

(** [add1 lst] adds 1 to each element of [lst]. *)


let add1 = transform (fun x -> x + 1)

(** [concat_bang lst] concatenates "!" to each element of [lst]. *)


let concat_bang = transform (fun x -> x ^ "!")

val transform : ('a -> 'b) -> 'a list -> 'b list = <fun>

val add1 : int list -> int list = <fun>

val concat_bang : string list -> string list = <fun>

Note: Instead of

let add1 lst = transform (fun x -> x + 1) lst

above we wrote

let add1 = transform (fun x -> x + 1)

This is another way of being higher order, but it’s one we already learned about under the guise of partial application.
The latter way of writing the function partially applies transform to just one of its two arguments, thus returning a
function. That function is bound to the name add1.

Indeed, the C++ library does call the equivalent function transform. But OCaml and many other languages (including
Java and Python) use the shorter word map, in the mathematical sense of how a function maps an input to an output. So
let’s make one final change to that name:

let rec map f = function


| [] -> []
| h :: t -> f h :: map f t
(continues on next page)

136 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)

(** [add1 lst] adds 1 to each element of [lst]. *)


let add1 = map (fun x -> x + 1)

(** [concat_bang lst] concatenates "!" to each element of [lst]. *)


let concat_bang = map (fun x -> x ^ "!")

val map : ('a -> 'b) -> 'a list -> 'b list = <fun>

val add1 : int list -> int list = <fun>

val concat_bang : string list -> string list = <fun>

We have now successfully applied the Abstraction Principle: the common structure has been factored out. What’s left
clearly expresses the computation, at least to the reader who is familiar with map, in a way that the original versions do
not as quickly make apparent.

6.2.1 Side Effects

The map function exists already in OCaml’s standard library as List.map, but with one small difference from the
implementation we discovered above. First, let’s see what’s potentially wrong with our own implementation, then we’ll
look at the standard library’s implementation.
We’ve seen before in our discussion of exceptions that the OCaml language specification does not generally specify evalu-
ation order of subexpressions, and that the current language implementation generally evaluates right-to-left. Because of
that, the following (rather contrived) code actually causes the list elements to be printed in what might seem like reverse
order:

let p x = print_int x; print_newline(); x + 1

let lst = map p [1; 2]

val p : int -> int = <fun>

val lst : int list = [2; 3]

Here’s why:
• Expression map p [1; 2] evaluates to p 1 :: map p [2].
• The right-hand side of that expression is then evaluated to p 1 :: (p 2 :: map p []). The application
of p to 1 has not yet occurred.
• The right-hand side of :: is again evaluated next, yielding p 1 :: (p 2 :: []).
• Then p is applied to 2, and finally to 1.

6.2. Map 137


OCaml Programming: Correct + Efficient + Beautiful

That is likely surprising to anyone who is predisposed to thinking that evaluation would occur left-to-right. The solution
is to use a let expression to cause the evaluation of the function application to occur before the recursive call:

let rec map f = function


| [] -> []
| h :: t -> let h' = f h in h' :: map f t

let lst2 = map p [1; 2]

val map : ('a -> 'b) -> 'a list -> 'b list = <fun>

val lst2 : int list = [2; 3]

Here’s why that works:


• Expression map p [1; 2] evaluates to let h' = p 1 in h' :: map p [2].
• The binding expression p 1 is evaluated, causing 1 to be printed and h' to be bound to 2.
• The body expression h' :: map p [2] is then evaluated, which leads to 2 being printed next.
So that’s how the standard library defines List.map. We should use it instead of re-defining the function ourselves from
now on. But it’s good that we have discovered the function “from scratch” as it were, and that if needed we could quickly
re-code it.
The bigger lesson to take away from this discussion is that when evaluation order matters, we need to use let to ensure
it. When does it matter? Only when there are side effects. Printing and exceptions are the two we’ve seen so far. Later
we’ll add mutability.

6.2.2 Map and Tail Recursion

Astute readers will have noticed that the implementation of map is not tail recursive. That is to some extent unavoidable.
Here’s a tempting but awful way to create a tail-recursive version of it:

let rec map_tr_aux f acc = function


| [] -> acc
| h :: t -> map_tr_aux f (acc @ [f h]) t

let map_tr f = map_tr_aux f []

let lst = map_tr (fun x -> x + 1) [1; 2; 3]

val map_tr_aux : ('a -> 'b) -> 'b list -> 'a list -> 'b list = <fun>

val map_tr : ('a -> 'b) -> 'a list -> 'b list = <fun>

val lst : int list = [2; 3; 4]

138 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

To some extent that works: the output is correct, and map_tr_aux is tail recursive. The subtle flaw is the subexpression
acc @ [f h]. Recall that append is a linear-time operation on singly-linked lists. That is, if there are 𝑛 list elements
then append takes time 𝑂(𝑛). So at each recursive call we perform a 𝑂(𝑛) operation. And there will be 𝑛 recursive calls,
one for each element of the list. That’s a total of 𝑛 ⋅ 𝑂(𝑛) work, which is 𝑂(𝑛2 ). So we achieved tail recursion, but at a
high cost: what ought to be a linear-time operation became quadratic time.
In an attempt to fix that, we could use the constant-time cons operation instead of the linear-time append operation:

let rec map_tr_aux f acc = function


| [] -> acc
| h :: t -> map_tr_aux f (f h :: acc) t

let map_tr f = map_tr_aux f []

let lst = map_tr (fun x -> x + 1) [1; 2; 3]

val map_tr_aux : ('a -> 'b) -> 'b list -> 'a list -> 'b list = <fun>

val map_tr : ('a -> 'b) -> 'a list -> 'b list = <fun>

val lst : int list = [4; 3; 2]

And to some extent that works: it’s tail recursive and linear time. The not-so-subtle flaw this time is that the output is
backwards. As we take each element off the front of the input list, we put it on the front of the output list, but that reverses
their order.

Note: To understand why the reversal occurs, it might help to think of the input and output lists as people standing in a
queue:
• Input: Alice, Bob.
• Output: empty.
Then we remove Alice from the input and add her to the output:
• Input: Bob.
• Output: Alice.
Then we remove Bob from the input and add him to the output:
• Input: empty.
• Output: Bob, Alice.
The point is that with singly-linked lists, we can only operate on the head of the list and still be constant time. We can’t
move Bob to the back of the output without making him walk past Alice—and anyone else who might be standing in the
output.

For that reason, the standard library calls this function List.rev_map, that is, a (tail-recursive) map function that
returns its output in reverse order.

let rec rev_map_aux f acc = function


| [] -> acc
| h :: t -> rev_map_aux f (f h :: acc) t

(continues on next page)

6.2. Map 139


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


let rev_map f = rev_map_aux f []

let lst = rev_map (fun x -> x + 1) [1; 2; 3]

val rev_map_aux : ('a -> 'b) -> 'b list -> 'a list -> 'b list = <fun>

val rev_map : ('a -> 'b) -> 'a list -> 'b list = <fun>

val lst : int list = [4; 3; 2]

If you want the output in the “right” order, that’s easy: just apply List.rev to it:

let lst = List.rev (List.rev_map (fun x -> x + 1) [1; 2; 3])

val lst : int list = [2; 3; 4]

Since List.rev is both linear time and tail recursive, that yields a complete solution. We get a linear-time and tail-
recursive map computation. The expense is that it requires two passes through the list: one to transform, the other
to reverse. We’re not going to do better than this efficiency with a singly-linked list. Of course, there are other data
structures that implement lists, and we’ll come to those eventually. Meanwhile, recall that we generally don’t have to
worry about tail recursion (which is to say, about stack space) until lists have 10,000 or more elements.
Why doesn’t the standard library provide this all-in-one function? Maybe it will someday if there’s good enough reason.
But you might discover in your own programming there’s not a lot of need for it. In many cases, we can either do without
the tail recursion, or be content with a reversed list.
The bigger lesson to take away from this discussion is that there can be a tradeoff between time and space efficiency for
recursive functions. By attempting to make a function more space efficient (i.e., tail recursive), we can accidentally make
it asymptotically less time efficient (i.e., quadratic instead of linear), or if we’re clever keep the asymptotic time efficiency
the same (i.e., linear) at the cost of a constant factor (i.e., processing twice).

6.2.3 Map in Other Languages

We mentioned above that the idea of map exists in many programming languages. Here’s an example from Python:

>>> print(list(map(lambda x: x + 1, [1, 2, 3])))


[2, 3, 4]

We have to use the list function to convert the result of the map back to a list, because Python for sake of efficiency
produces each element of the map output as needed. Here again we see the theme of “when does it get evaluated?”
returning.
In Java, map is part of the Stream abstraction that was added in Java 8. Since there isn’t a built-in Java syntax for lists
or streams, it’s a little more verbose to give an example. Here we use a factory method Stream.of to create a stream:

jshell> Stream.of(1, 2, 3).map(x -> x + 1).collect(Collectors.toList())


$1 ==> [2, 3, 4]

Like in the Python example, we have to use something to convert the stream back into a list. In this case it’s the collect
method.

140 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

6.3 Filter

Suppose we wanted to filter out only the even numbers from a list, or the odd numbers. Here are some functions to do
that:

(** [even n] is whether [n] is even. *)


let even n =
n mod 2 = 0

(** [evens lst] is the sublist of [lst] containing only even numbers. *)
let rec evens = function
| [] -> []
| h :: t -> if even h then h :: evens t else evens t

let lst1 = evens [1; 2; 3; 4]

val even : int -> bool = <fun>

val evens : int list -> int list = <fun>

val lst1 : int list = [2; 4]

(** [odd n] is whether [n] is odd. *)


let odd n =
n mod 2 <> 0

(** [odds lst] is the sublist of [lst] containing only odd numbers. *)
let rec odds = function
| [] -> []
| h :: t -> if odd h then h :: odds t else odds t

let lst2 = odds [1; 2; 3; 4]

val odd : int -> bool = <fun>

val odds : int list -> int list = <fun>

val lst2 : int list = [1; 3]

Functions evens and odds are nearly the same code: the only essential difference is the test they apply to the head
element. So as we did with map in the previous section, let’s factor out that test as a function. Let’s name the function p
as short for “predicate”, which is a fancy way of saying that it tests whether something is true or false:

let rec filter p = function


| [] -> []
| h :: t -> if p h then h :: filter p t else filter p t

val filter : ('a -> bool) -> 'a list -> 'a list = <fun>

And now we can reimplement our original two functions:

6.3. Filter 141


OCaml Programming: Correct + Efficient + Beautiful

let evens = filter even


let odds = filter odd

val evens : int list -> int list = <fun>

val odds : int list -> int list = <fun>

How simple these are! How clear! (At least to the reader who is familiar with filter.)

6.3.1 Filter and Tail Recursion

As we did with map, we can create a tail-recursive version of filter:

let rec filter_aux p acc = function


| [] -> acc
| h :: t -> if p h then filter_aux p (h :: acc) t else filter_aux p acc t

let filter p = filter_aux p []

let lst = filter even [1; 2; 3; 4]

val filter_aux : ('a -> bool) -> 'a list -> 'a list -> 'a list = <fun>

val filter : ('a -> bool) -> 'a list -> 'a list = <fun>

val lst : int list = [4; 2]

And again we discover the output is backwards. Here, the standard library makes a different choice than it did with map.
It builds in the reversal to List.filter, which is implemented like this:

let rec filter_aux p acc = function


| [] -> List.rev acc (* note the built-in reversal *)
| h :: t -> if p h then filter_aux p (h :: acc) t else filter_aux p acc t

let filter p = filter_aux p []

val filter_aux : ('a -> bool) -> 'a list -> 'a list -> 'a list = <fun>

val filter : ('a -> bool) -> 'a list -> 'a list = <fun>

Why does the standard library treat map and filter differently on this point? Good question. Perhaps there has simply
never been a demand for a filter function whose time efficiency is a constant factor better. Or perhaps it is just
historical accident.

142 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

6.3.2 Filter in Other Languages

Again, the idea of filter exists in many programming languages. Here it is in Python:

>>> print(list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4])))


[2, 4]

And in Java:

jshell> Stream.of(1, 2, 3, 4).filter(x -> x % 2 == 0).collect(Collectors.toList())


$1 ==> [2, 4]

6.4 Fold

The map function gives us a way to individually transform each element of a list. The filter function gives us a way to
individually decide whether to keep or throw away each element of a list. But both of those are really just looking at a
single element at a time. What if we wanted to somehow combine all the elements of a list? That’s what the fold function
is for. It turns out that there are two versions of it, which we’ll study in this section. But to start, let’s look at a related
function—not actually in the standard library—that we call combine.

6.4.1 Combine

Once more, let’s write two functions:

(** [sum lst] is the sum of all the elements of [lst]. *)


let rec sum = function
| [] -> 0
| h :: t -> h + sum t

let s = sum [1; 2; 3]

val sum : int list -> int = <fun>

val s : int = 6

(** [concat lst] is the concatenation of all the elements of [lst]. *)


let rec concat = function
| [] -> ""
| h :: t -> h ^ concat t

let c = concat ["a"; "b"; "c"]

val concat : string list -> string = <fun>

val c : string = "abc"

As when we went through similar exercises with map and filter, the functions share a great deal of common structure.
The differences here are:

6.4. Fold 143


OCaml Programming: Correct + Efficient + Beautiful

• the case for the empty list returns a different initial value, 0 vs ""
• the case of a non-empty list uses a different operator to combine the head element with the result of the recursive
call, + vs ^.
So can we apply the Abstraction Principle again? Sure! But this time we need to factor out two arguments: one for each
of those two differences.
To start, let’s factor out only the initial value:

let rec sum' init = function


| [] -> init
| h :: t -> h + sum' init t

let sum = sum' 0

let rec concat' init = function


| [] -> init
| h :: t -> h ^ concat' init t

let concat = concat' ""

val sum' : int -> int list -> int = <fun>

val sum : int list -> int = <fun>

val concat' : string -> string list -> string = <fun>

val concat : string list -> string = <fun>

Now the only real difference left between sum' and concat' is the operator used to combine the head with the recursive
call on the tail. That operator can also become an argument to a unified function we call combine:

let rec combine op init = function


| [] -> init
| h :: t -> op h (combine op init t)

let sum = combine ( + ) 0


let concat = combine ( ^ ) ""

val combine : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b = <fun>

val sum : int list -> int = <fun>

val concat : string list -> string = <fun>

One way to think of combine would be that:


• the [] value in the list gets replaced by init, and
• each :: constructor gets replaced by op.
For example, [a; b; c] is just syntactic sugar for a :: (b :: (c :: [])). So if we replace [] with 0 and
:: with (+), we get a + (b + (c + 0)). And that would be the sum of the list.

144 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

Once more, the Abstraction Principle has led us to an amazingly simple and succinct expression of the computation.

6.4.2 Fold Right

The combine function is the idea underlying an actual OCaml library function. To get there, we need to make a couple
of changes to the implementation we have so far.
First, let’s rename some of the arguments: we’ll change op to f to emphasize that really we could pass in any function,
not just a built-in operator like +. And we’ll change init to acc, which as usual stands for “accumulator”. That yields:

let rec combine f acc = function


| [] -> acc
| h :: t -> f h (combine f acc t)

val combine : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b = <fun>

Second, let’s make an admittedly less well-motivated change. We’ll swap the implicit list argument to combine with the
init argument:

let rec combine' f lst acc = match lst with


| [] -> acc
| h :: t -> f h (combine' f t acc)

let sum lst = combine' ( + ) lst 0


let concat lst = combine' ( ^ ) lst ""

val combine' : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun>

val sum : int list -> int = <fun>

val concat : string list -> string = <fun>

It’s a little less convenient to code the function this way, because we no longer get to take advantage of the function
keyword, nor of partial application in defining sum and concat. But there’s no algorithmic change.
What we now have is the actual implementation of the standard library function List.fold_right. All we have left
to do is change the function name:

let rec fold_right f lst acc = match lst with


| [] -> acc
| h :: t -> f h (fold_right f t acc)

val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun>

Why is this function called “fold right”? The intuition is that the way it works is to “fold in” elements of the list from
the right to the left, combining each new element using the operator. For example, fold_right ( + ) [a; b;
c] 0 results in evaluation of the expression a + (b + (c + 0)). The parentheses associate from the right-most
subexpression to the left.

6.4. Fold 145


OCaml Programming: Correct + Efficient + Beautiful

6.4.3 Tail Recursion and Combine

Neither fold_right nor combine are tail recursive: after the recursive call returns, there is still work to be done in
applying the function argument f or op. Let’s go back to combine and rewrite it to be tail recursive. All that requires
is to change the cons branch:

let rec combine_tr f acc = function


| [] -> acc
| h :: t -> combine_tr f (f acc h) t (* only real change *)

val combine_tr : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

(Careful readers will notice that the type of combine_tr is different than the type of combine. We will address that
soon.)
Now the function f is applied to the head element h and the accumulator acc before the recursive call is made, thus
ensuring there’s no work remaining to be done after the call returns. If that seems a little mysterious, here’s a rewriting
of the two functions that might help:

let rec combine f acc = function


| [] -> acc
| h :: t ->
let acc' = combine f acc t in
f h acc'

let rec combine_tr f acc = function


| [] -> acc
| h :: t ->
let acc' = f acc h in
combine_tr f acc' t

val combine : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b = <fun>

val combine_tr : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

Pay close attention to the definition of acc', the new accumulator, in each of those versions:
• In the original version, we procrastinate using the head element h. First, we combine all the remaining tail elements
to get acc'. Only then do we use f to fold in the head. So the value passed as the initial value of acc turns out
to be the same for every recursive invocation of combine: it’s passed all the way down to where it’s needed, at
the right-most element of the list, then used there exactly once.
• But in the tail recursive version, we “pre-crastinate” by immediately folding h in with the old accumulator acc.
Then we fold that in with all the tail elements. So at each recursive invocation, the value passed as the argument
acc can be different.
The tail recursive version of combine works just fine for summation (and concatenation, which we elide):

let sum = combine_tr ( + ) 0


let s = sum [1; 2; 3]

val sum : int list -> int = <fun>

146 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

val s : int = 6

But something possibly surprising happens with subtraction:

let sub = combine ( - ) 0


let s = sub [3; 2; 1]

let sub_tr = combine_tr ( - ) 0


let s' = sub_tr [3; 2; 1]

val sub : int list -> int = <fun>

val s : int = 2

val sub_tr : int list -> int = <fun>

val s' : int = -6

The two results are different!


• With combine we compute 3 - (2 - (1 - 0)). First we fold in 1, then 2, then 3. We are processing the
list from right to left, putting the initial accumulator at the far right.
• But with combine_tr we compute (((0 - 3) - 2) - 1). We are processing the list from left to right,
putting the initial accumulator at the far left.
With addition it didn’t matter which order we processed the list, because addition is associative and commutative. But
subtraction is not, so the two directions result in different answers.
Actually this shouldn’t be too surprising if we think back to when we made map be tail recursive. Then, we discovered
that tail recursion can cause us to process the list in reverse order from the non-tail recursive version of the same function.
That’s what happened here.

6.4.4 Fold Left

Our combine_tr function is also in the standard library under the name List.fold_left:

let rec fold_left f acc = function


| [] -> acc
| h :: t -> fold_left f (f acc h) t

let sum = fold_left ( + ) 0


let concat = fold_left ( ^ ) ""

val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

val sum : int list -> int = <fun>

val concat : string list -> string = <fun>

We have once more succeeded in applying the Abstraction Principle.

6.4. Fold 147


OCaml Programming: Correct + Efficient + Beautiful

6.4.5 Fold Left vs. Fold Right

Let’s review the differences between fold_right and fold_left:


• They combine list elements in opposite orders, as indicated by their names. Function fold_right combines
from the right to the left, whereas fold_left proceeds from the left to the right.
• Function fold_left is tail recursive whereas fold_right is not.
• The types of the functions are different.
Regarding that final point, it can be hard to remember what those types are! Luckily we can always ask the toplevel:

List.fold_left;;
List.fold_right;;

- : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

- : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun>

To understand those types, look for the list argument in each one of them. That tells you the type of the values in the
list. Then look for the type of the return value; that tells you the type of the accumulator. From there you can work out
everything else.
• In fold_left, the list argument is of type 'b list, so the list contains values of type 'b. The return type is
'a, so the accumulator has type 'a. Knowing that, we can figure out that the second argument is the initial value
of the accumulator (because it has type 'a). And we can figure out that the first argument, the combining operator,
takes as its own first argument an accumulator value (because it has type 'a), as its own second argument a list
element (because it has type 'b), and returns a new accumulator value.
• In fold_right, the list argument is of type 'a list, so the list contains values of type 'a. The return type is
'b, so the accumulator has type 'b. Knowing that, we can figure out that the third argument is the initial value of
the accumulator (because it has type 'b). And we can figure out that the first argument, the combining operator,
takes as its own second argument an accumulator value (because it has type 'b), as its own first argument a list
element (because it has type 'a), and returns a new accumulator value.

Tip: You might wonder why the argument orders are different between the two fold functions. Good question. Other
libraries do in fact use different argument orders. One way to remember it for OCaml is that in fold_X the accumulator
argument goes to the X of the list argument.

If you find it hard to keep track of all these argument orders, the ListLabels module in the standard library can help.
It uses labeled arguments to give names to the combining operator (which it calls f) and the initial accumulator value
(which it calls init). Internally, the implementation is actually identical to the List module.

ListLabels.fold_left;;
ListLabels.fold_left ~f:(fun x y -> x - y) ~init:0 [1;2;3];;

- : f:('a -> 'b -> 'a) -> init:'a -> 'b list -> 'a = <fun>

- : int = -6

ListLabels.fold_right;;
ListLabels.fold_right ~f:(fun y x -> x - y) ~init:0 [1;2;3];;

148 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

- : f:('a -> 'b -> 'b) -> 'a list -> init:'b -> 'b = <fun>

- : int = -6

Notice how in the two applications of fold above, we are able to write the arguments in a uniform order thanks to their
labels. However, we still have to be careful about which argument to the combining operator is the list element vs. the
accumulator value.

6.4.6 A Digression on Labeled Arguments and Fold

It’s possible to write our own version of the fold functions that would label the arguments to the combining operator, so
we don’t even have to remember their order:

let rec fold_left ~op:(f: acc:'a -> elt:'b -> 'a) ~init:acc lst =
match lst with
| [] -> acc
| h :: t -> fold_left ~op:f ~init:(f ~acc:acc ~elt:h) t

let rec fold_right ~op:(f: elt:'a -> acc:'b -> 'b) lst ~init:acc =
match lst with
| [] -> acc
| h :: t -> f ~elt:h ~acc:(fold_right ~op:f t ~init:acc)

val fold_left : op:(acc:'a -> elt:'b -> 'a) -> init:'a -> 'b list -> 'a =
<fun>

val fold_right : op:(elt:'a -> acc:'b -> 'b) -> 'a list -> init:'b -> 'b =
<fun>

But those functions aren’t as useful as they might seem:

let s = fold_left ~op:( + ) ~init:0 [1;2;3]

File "[17]", line 1, characters 22-27:


1 | let s = fold_left ~op:( + ) ~init:0 [1;2;3]
^^^^^
Error: This expression has type int -> int -> int
but an expression was expected of type acc:'a -> elt:'b -> 'a

The problem is that the built-in + operator doesn’t have labeled arguments, so we can’t pass it in as the combining operator
to our labeled functions. We’d have to define our own labeled version of it:

let add ~acc ~elt = acc + elt


let s = fold_left ~op:add ~init:0 [1; 2; 3]

But now we have to remember that the ~acc parameter to add will become the left-hand argument to ( + ). That’s
not really much of an improvement over what we had to remember to begin with.

6.4. Fold 149


OCaml Programming: Correct + Efficient + Beautiful

6.4.7 Using Fold to Implement Other Functions

Folding is so powerful that we can write many other list functions in terms of fold_left or fold_right. For
example,

let length lst =


List.fold_left (fun acc _ -> acc + 1) 0 lst

let rev lst =


List.fold_left (fun acc x -> x :: acc) [] lst

let map f lst =


List.fold_right (fun x acc -> f x :: acc) lst []

let filter f lst =


List.fold_right (fun x acc -> if f x then x :: acc else acc) lst []

val length : 'a list -> int = <fun>

val rev : 'a list -> 'a list = <fun>

val map : ('a -> 'b) -> 'a list -> 'b list = <fun>

val filter : ('a -> bool) -> 'a list -> 'a list = <fun>

At this point it begins to become debatable whether it’s better to express the computations above using folding or using
the ways we have already seen. Even for an experienced functional programmer, understanding what a fold does can
take longer than reading the naive recursive implementation. If you peruse the source code of the standard library, you’ll
see that none of the List module internally is implemented in terms of folding, which is perhaps one comment on the
readability of fold. On the other hand, using fold ensures that the programmer doesn’t accidentally program the recursive
traversal incorrectly. And for a data structure that’s more complicated than lists, that robustness might be a win.

6.4.8 Fold vs. Recursive vs. Library

We’ve now seen three different ways for writing functions that manipulate lists:
• directly as a recursive function that pattern matches against the empty list and against cons,
• using fold functions, and
• using other library functions.
Let’s try using each of those ways to solve a problem, so that we can appreciate them better.
Consider writing a function lst_and: bool list -> bool, such that lst_and [a1; ...; an] returns
whether all elements of the list are true. That is, it evaluates the same as a1 && a2 && ... && an. When applied
to an empty list, it evaluates to true.
Here are three possible ways of writing such a function. We give each way a slightly different function name for clarity.

let rec lst_and_rec = function


| [] -> true
| h :: t -> h && lst_and_rec t

(continues on next page)

150 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


let lst_and_fold =
List.fold_left (fun acc elt -> acc && elt) true

let lst_and_lib =
List.for_all (fun x -> x)

val lst_and_rec : bool list -> bool = <fun>

val lst_and_fold : bool list -> bool = <fun>

val lst_and_lib : bool list -> bool = <fun>

The worst-case running time of all three functions is linear in the length of the list. But:
• The first function, lst_and_rec has the advantage that it need not process the entire list. It will immediately
return false the first time they discover a false element in the list.
• The second function, lst_and_fold, will always process every element of the list.
• As for the third function lst_and_lib, according to the documentation of List.for_all, it returns (p
a1) && (p a2) && ... && (p an). So like lst_and_rec it need not process every element.

6.5 Beyond Lists

Functionals like map and fold are not restricted to lists. They make sense for nearly any kind of data collection. For
example, recall this tree representation:

type 'a tree =


| Leaf
| Node of 'a * 'a tree * 'a tree

type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree

6.5.1 Map on Trees

This one is easy. All we have to do is apply the function f to the value v at each node:

let rec map_tree f = function


| Leaf -> Leaf
| Node (v, l, r) -> Node (f v, map_tree f l, map_tree f r)

val map_tree : ('a -> 'b) -> 'a tree -> 'b tree = <fun>

6.5. Beyond Lists 151


OCaml Programming: Correct + Efficient + Beautiful

6.5.2 Fold on Trees

This one is only a little harder. Let’s develop a fold functional for 'a tree similar to our fold_right over 'a list.
One way to think of List.fold_right would be that the [] value in the list gets replaced by the acc argument, and
each :: constructor gets replaced by an application of the f argument. For example, [a; b; c] is syntactic sugar for
a :: (b :: (c :: [])). So if we replace [] with 0 and :: with ( + ), we get a + (b + (c + 0)).
Along those lines, here’s a way we could rewrite fold_right that will help us think a little more clearly:

type 'a mylist =


| Nil
| Cons of 'a * 'a mylist

let rec fold_mylist f acc = function


| Nil -> acc
| Cons (h, t) -> f h (fold_mylist f acc t)

type 'a mylist = Nil | Cons of 'a * 'a mylist

val fold_mylist : ('a -> 'b -> 'b) -> 'b -> 'a mylist -> 'b = <fun>

The algorithm is the same. All we’ve done is to change the definition of lists to use constructors written with alphabetic
characters instead of punctuation, and to change the argument order of the fold function.
For trees, we’ll want the initial value of acc to replace each Leaf constructor, just like it replaced [] in lists. And
we’ll want each Node constructor to be replaced by the operator. But now the operator will need to be ternary instead of
binary—that is, it will need to take three arguments instead of two—because a tree node has a value, a left child, and a
right child, whereas a list cons had only a head and a tail.
Inspired by those observations, here is the fold function on trees:

let rec fold_tree f acc = function


| Leaf -> acc
| Node (v, l, r) -> f v (fold_tree f acc l) (fold_tree f acc r)

val fold_tree : ('a -> 'b -> 'b -> 'b) -> 'b -> 'a tree -> 'b = <fun>

If you compare that function to fold_mylist, you’ll note it very nearly identical. There’s just one more recursive call
in the second pattern-matching branch, corresponding to the one more occurrence of 'a tree in the definition of that
type.
We can then use fold_tree to implement some of the tree functions we’ve previously seen:

let size t = fold_tree (fun _ l r -> 1 + l + r) 0 t


let depth t = fold_tree (fun _ l r -> 1 + max l r) 0 t
let preorder t = fold_tree (fun x l r -> [x] @ l @ r) [] t

val size : 'a tree -> int = <fun>

val depth : 'a tree -> int = <fun>

val preorder : 'a tree -> 'a list = <fun>

152 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

Why did we pick fold_right and not fold_left for this development? Because fold_left is tail recursive,
which is something we’re never going to achieve on binary trees. Suppose we process the left branch first; then we still
have to process the right branch before we can return. So there will always be work left to do after a recursive call on one
branch. Thus, on trees an equivalent to fold_right is the best which we can hope for.
The technique we used to derive fold_tree works for any OCaml variant type t:
• Write a recursive fold function that takes in one argument for each constructor of t.
• That fold function matches against the constructors, calling itself recursively on any value of type t that it en-
counters.
• Use the appropriate argument of fold to combine the results of all recursive calls as well as all data not of type t
at each constructor.
This technique constructs something called a catamorphism, aka a generalized fold operation. To learn more about cata-
morphisms, take a course on category theory.

6.5.3 Filter on Trees

This one is perhaps the hardest to design. The problem is: if we decide to filter a node, what should we do with its
children?
• We could recurse on the children. If after filtering them only one child remains, we could promote it in place of
its parent. But what if both children remain, or neither? Then we’d somehow have to reshape the tree. Without
knowing more about how the tree is intended to be used—that is, what kind of data it represents—we are stuck.
• Instead, we could just eliminate the children entirely. So the decision to filter a node means pruning the entire
subtree rooted at that node.
The latter is easy to implement:

let rec filter_tree p = function


| Leaf -> Leaf
| Node (v, l, r) ->
if p v then Node (v, filter_tree p l, filter_tree p r) else Leaf

val filter_tree : ('a -> bool) -> 'a tree -> 'a tree = <fun>

6.6 Pipelining

Suppose we wanted to compute the sum of squares of the numbers from 0 up to 𝑛. How might we go about it? Of course
(math being the best form of optimization), the most efficient way would be a closed-form formula:

𝑛(𝑛 + 1)(2𝑛 + 1)
6
But let’s imagine you’ve forgotten that formula. In an imperative language you might use a for loop:

# Python
def sum_sq(n):
sum = 0
for i in range(0, n+1):
sum += i * i
return sum

6.6. Pipelining 153


OCaml Programming: Correct + Efficient + Beautiful

The equivalent (tail) recursive code in OCaml would be:

let sum_sq n =
let rec loop i sum =
if i > n then sum
else loop (i + 1) (sum + i * i)
in loop 0 0

val sum_sq : int -> int = <fun>

Another, clearer way of producing the same result in OCaml uses higher-order functions and the pipeline operator:

let rec ( -- ) i j = if i > j then [] else i :: i + 1 -- j


let square x = x * x
let sum = List.fold_left ( + ) 0

let sum_sq n =
0 -- n (* [0;1;2;...;n] *)
|> List.map square (* [0;1;4;...;n*n] *)
|> sum (* 0+1+4+...+n*n *)

val ( -- ) : int -> int -> int list = <fun>

val square : int -> int = <fun>

val sum : int list -> int = <fun>

val sum_sq : int -> int = <fun>

The function sum_sq first constructs a list containing all the numbers 0..n. Then it uses the pipeline operator |> to
pass that list through List.map square, which squares every element. Then the resulting list is pipelined through
sum, which adds all the elements together.
The other alternatives that you might consider are somewhat uglier:

(* Maybe worse: a lot of extra [let..in] syntax and unnecessary names to


for intermediate values we don't care about. *)
let sum_sq n =
let l = 0 -- n in
let sq_l = List.map square l in
sum sq_l

(* Maybe worse: have to read the function applications from right to left
rather than top to bottom, and extra parentheses. *)
let sum_sq n =
sum (List.map square (0--n))

val sum_sq : int -> int = <fun>

val sum_sq : int -> int = <fun>

The downside of all of these compared to the original tail recursive version is that they are wasteful of space—linear

154 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

instead of constant—and take a constant factor more time. So as is so often the case in programming, there is a tradeoff
between clarity and efficiency of code.
Note that the inefficiency is not from the pipeline operator itself, but from having to construct all those unnecessary
intermediate lists. So don’t get the idea that pipelining is intrinsically bad. In fact, it can be quite useful. When we get to
the chapter on modules, we’ll use it quite often with some of the data structures we study there.

6.7 Currying

We’ve already seen that an OCaml function that takes two arguments of types t1 and t2 and returns a value of type t3
has the type t1 -> t2 -> t3. We use two variables after the function name in the let expression:

let add x y = x + y

val add : int -> int -> int = <fun>

Another way to define a function that takes two arguments is to write a function that takes a tuple:

let add' t = fst t + snd t

val add' : int * int -> int = <fun>

Instead of using fst and snd, we could use a tuple pattern in the definition of the function, leading to a third imple-
mentation:

let add'' (x, y) = x + y

val add'' : int * int -> int = <fun>

Functions written using the first style (with type t1 -> t2 -> t3) are called curried functions, and functions using
the second style (with type t1 * t2 -> t3) are called uncurried. Metaphorically, curried functions are “spicier”
because you can partially apply them (something you can’t do with uncurried functions: you can’t pass in half of a pair).
Actually, the term curry does not refer to spices, but to a logician named Haskell Curry (one of a very small set of people
with programming languages named after both their first and last names).
Sometimes you will come across libraries that offer an uncurried version of a function, but you want a curried version of
it to use in your own code; or vice versa. So it is useful to know how to convert between the two kinds of functions, as
we did with add above.
You could even write a couple of higher-order functions to do the conversion for you:

let curry f x y = f (x, y)


let uncurry f (x, y) = f x y

val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c = <fun>

val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c = <fun>

let uncurried_add = uncurry add


let curried_add = curry add''

6.7. Currying 155


OCaml Programming: Correct + Efficient + Beautiful

val uncurried_add : int * int -> int = <fun>

val curried_add : int -> int -> int = <fun>

6.8 Summary

This chapter is one of the most important in the book. It didn’t cover any new language features. Instead, we learned how
to use some of the existing features in ways that might be new, surprising, or challenging. Higher-order programming and
the Abstraction Principle are two ideas that will help make you a better programmer in any language, not just OCaml. Of
course, languages do vary in the extent to which they support these ideas, with some providing significantly less assistance
in writing higher-order code—which is one reason we use OCaml in this course.
Map, filter, fold and other functionals are becoming widely recognized as excellent ways to structure computation. Part
of the reason for that is they factor out the iteration over a data structure from the computation done at each element.
Languages such as Python, Ruby, and Java 8 now have support for this kind of iteration.

6.8.1 Terms and Concepts

• Abstraction Principle
• accumulator
• apply
• associative
• compose
• factor
• filter
• first-order function
• fold
• functional
• generalized fold operation
• higher-order function
• map
• pipeline
• pipelining

156 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

6.8.2 Further Reading

• Introduction to Objective Caml, chapters 3.1.3, 5.3


• OCaml from the Very Beginning, chapter 6
• More OCaml: Algorithms, Methods, and Diversions, chapter 1, by John Whitington. This book is a sequel to OCaml
from the Very Beginning.
• Real World OCaml, chapter 3 (beware that this book’s Core library has a different List module than the standard
library’s List module, with different types for map and fold than those we saw here)
• “Higher Order Functions”, chapter 6 of Functional Programming: Practice and Theory. Bruce J. MacLennan,
Addison-Wesley, 1990. Our discussion of higher-order functions and the Abstraction Principle is indebted to this
chapter.
• “Can Programming be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs.”
John Backus’ 1977 Turing Award lecture in its elaborated form as a published article.
• “Second-order and Higher-order Logic” in The Stanford Encyclopedia of Philosophy.

6.9 Exercises

Solutions to most exercises are available. Fall 2022 is the first public release of these solutions. Though they have been
available to Cornell students for a few years, it is inevitable that wider circulation will reveal improvements that could be
made. We are happy to add or correct solutions. Please make contributions through GitHub.

Exercise: twice, no arguments [★]


Consider the following definitions:

let double x = 2 * x
let square x = x * x
let twice f x = f (f x)
let quad = twice double
let fourth = twice square

Use the toplevel to determine what the types of quad and fourth are. Explain how it can be that quad is not syntac-
tically written as a function that takes an argument, and yet its type shows that it is in fact a function.

Exercise: mystery operator 1 [★★]


What does the following operator do?

let ( $ ) f x = f x

Hint: investigate square $ 2 + 2 vs. square 2 + 2.

Exercise: mystery operator 2 [★★]


What does the following operator do?

6.9. Exercises 157


OCaml Programming: Correct + Efficient + Beautiful

let ( @@ ) f g x = x |> g |> f

Hint: investigate String.length @@ string_of_int applied to 1, 10, 100, etc.

Exercise: repeat [★★]


Generalize twice to a function repeat, such that repeat f n x applies f to x a total of n times. That is,
• repeat f 0 x yields x
• repeat f 1 x yields f x
• repeat f 2 x yields f (f x) (which is the same as twice f x)
• repeat f 3 x yields f (f (f x))
• …

Exercise: product [★]


Use fold_left to write a function product_left that computes the product of a list of floats. The product of the
empty list is 1.0. Hint: recall how we implemented sum in just one line of code in lecture.
Use fold_right to write a function product_right that computes the product of a list of floats. Same hint applies.

Exercise: terse product [★★]


How terse can you make your solutions to the product exercise? Hints: you need only one line of code for each, and
you do not need the fun keyword. For fold_left, your function definition does not even need to explicitly take a list
argument. If you use ListLabels, the same is true for fold_right.

Exercise: sum_cube_odd [★★]


Write a function sum_cube_odd n that computes the sum of the cubes of all the odd numbers between 0 and n
inclusive. Do not write any new recursive functions. Instead, use the functionals map, fold, and filter, and the ( -- )
operator (defined in the discussion of pipelining).

Exercise: sum_cube_odd pipeline [★★]


Rewrite the function sum_cube_odd to use the pipeline operator |>.

Exercise: exists [★★]


Consider writing a function exists: ('a -> bool) -> 'a list -> bool, such that exists p [a1;
...; an] returns whether at least one element of the list satisfies the predicate p. That is, it evaluates the same as (p
a1) || (p a2) || ... || (p an). When applied to an empty list, it evaluates to false.
Write three solutions to this problem, as we did above:
• exists_rec, which must be a recursive function that does not use the List module,
• exists_fold, which uses either List.fold_left or List.fold_right, but not any other List mod-
ule functions nor the rec keyword, and

158 Chapter 6. Higher-Order Programming


OCaml Programming: Correct + Efficient + Beautiful

• exists_lib, which uses any combination of List module functions other than fold_left or
fold_right, and does not use the rec keyword.

Exercise: account balance [★★★]


Write a function which, given a list of numbers representing debits, deducts them from an account balance, and finally
returns the remaining amount in the balance. Write three versions: fold_left, fold_right, and a direct recursive
implementation.

Exercise: library uncurried [★★]


Here is an uncurried version of List.nth:

let uncurried_nth (lst, n) = List.nth lst n

In a similar way, write uncurried versions of these library functions:


• List.append
• Char.compare
• Stdlib.max

Exercise: map composition [★★★]


Show how to replace any expression of the form List.map f (List.map g lst) with an equivalent expression
that calls List.map only once.

Exercise: more list fun [★★★]


Write functions that perform the following computations. Each function that you write should use one of List.fold,
List.map or List.filter. To choose which of those to use, think about what the computation is doing: combining,
transforming, or filtering elements.
• Find those elements of a list of strings whose length is strictly greater than 3.
• Add 1.0 to every element of a list of floats.
• Given a list of strings strs and another string sep, produce the string that contains every element of strs
separated by sep. For example, given inputs ["hi";"bye"] and ",", produce "hi,bye", being sure not to
produce an extra comma either at the beginning or end of the result string.

Exercise: association list keys [★★★]


Recall that an association list is an implementation of a dictionary in terms of a list of pairs, in which we treat the first
component of each pair as a key and the second component as a value.
Write a function keys: ('a * 'b) list -> 'a list that returns a list of the unique keys in an association
list. Since they must be unique, no value should appear more than once in the output list. The order of values output does
not matter. How compact and efficient can you make your solution? Can you do it in one line and linearithmic space and
time? Hint: List.sort_uniq.

Exercise: valid matrix [★★★]

6.9. Exercises 159


OCaml Programming: Correct + Efficient + Beautiful

A mathematical matrix can be represented with lists. In row-major representation, this matrix

1 1 1
[ ]
9 8 7

would be represented as the list [[1; 1; 1]; [9; 8; 7]]. Let’s represent a row vector as an int list. For
example, [9; 8; 7] is a row vector.
A valid matrix is an int list list that has at least one row, at least one column, and in which every column has
the same number of rows. There are many values of type int list list that are invalid, for example,
• []
• [[1; 2]; [3]]
Implement a function is_valid_matrix: int list list -> bool that returns whether the input matrix is
valid. Unit test the function.

Exercise: row vector add [★★★]


Implement a function add_row_vectors: int list -> int list -> int list for the element-wise
addition of two row vectors. For example, the addition of [1; 1; 1] and [9; 8; 7] is [10; 9; 8]. If the two
vectors do not have the same number of entries, the behavior of your function is unspecified—that is, it may do whatever
you like. Hint: there is an elegant one-line solution using List.map2. Unit test the function.

Exercise: matrix add [★★★]


Implement a function add_matrices: int list list -> int list list -> int list list
for matrix addition. If the two input matrices are not the same size, the behavior is unspecified. Hint: there is an elegant
one-line solution using List.map2 and add_row_vectors. Unit test the function.

Exercise: matrix multiply [★★★★]


Implement a function multiply_matrices: int list list -> int list list -> int list
list for matrix multiplication. If the two input matrices are not of sizes that can be multiplied together, the behavior is
unspecified. Unit test the function. Hint: define functions for matrix transposition and row vector dot product.

160 Chapter 6. Higher-Order Programming


CHAPTER

SEVEN

MODULAR PROGRAMMING

When a program is small enough, we can keep all of the details of the program in our heads at once. But real-world
applications can be many order of magnitude larger than those we write in college classes. They are simply too large
and complex to hold all their details in our heads. They are also written by many programmers. To build large software
systems requires techniques we haven’t talked about so far.
One key solution to managing complexity of large software is modular programming: the code is composed of many
different code modules that are developed separately. This allows different developers to take on discrete pieces of the
system and design and implement them without having to understand all the rest. But to build large programs out of
modules effectively, we need to be able to write modules that we can convince ourselves are correct in isolation from the
rest of the program. Rather than have to think about every other part of the program when developing a code module,
we need to be able to use local reasoning: that is, reasoning about just the module and the contract it needs to satisfy
with respect to the rest of the program. If everyone has done their job, separately developed code modules can be
plugged together to form a working program without every developer needing to understand everything done by every
other developer in the team. This is the key idea of modular programming.
Therefore, to build large programs that work, we must use abstraction to make it manageable to think about the pro-
gram. Abstraction is simply the removal of detail. A well-written program has the property that we can think about its
components (such as functions) abstractly, without concerning ourselves with all the details of how those components are
implemented.
Modules are abstracted by giving specifications of what they are supposed to do. A good module specification is clear,
understandable, and gives just enough information about what the module does for clients to successfully use it. This
abstraction makes the programmer’s job much easier; it is helpful even when there is only one programmer working on a
moderately large program, and it is crucial when there is more than one programmer.
Industrial-strength languages contain mechanisms that support modular programming. In general (i.e., across program-
ming languages), a module specification is known as an interface, which provides information to clients about the module’s
functionality while hiding the implementation. Object-oriented languages support modular programming with classes. The
Java interface construct is one example of a mechanism for specifying the interface to a class. A Java interface
informs clients of the available functionality in any class that implements it without revealing the details of the imple-
mentation. But even just the public methods of a class constitute an interface in the more general sense—an abstract
description of what the module can do.
Developers working with a module take on distinct roles. Most developers are usually clients of the module who under-
stand the interface but do not need to understand the implementation of the module. A developer who works on the
module implementation is naturally called an implementer. The module interface is a contract between the client and the
implementer, defining the responsibilities of both. Contracts are very important because they help us to isolate the source
of the problem when something goes wrong—and to know who to blame!
It is good practice to involve both clients and implementers in the design of a module’s interface. Interfaces designed
solely by one or the other can be seriously deficient. Each side will have its own view of what the final product should
look like, and these may not align! So mutual agreement on the contract is essential. It is also important to think hard
about global module structure and interfaces early, because changing an interface becomes more and more difficult as the
development proceeds and more of the code comes to depend on it.

161
OCaml Programming: Correct + Efficient + Beautiful

Modules should be used only through their declared interfaces, which the language should help to enforce. This is true
even when the client and the implementer are the same person. Modules decouple the system design and implementation
problem into separate tasks that can be carried out largely independently. When a module is used only through its interface,
the implementer has the flexibility to change the module as long as the module still satisfies its interface.

7.1 Module Systems

A programming language’s module system is the set of features it provides in support of modular programming. Below
are some common concerns of module systems. We focus on Java and OCaml in this discussion, mentioning some of the
most related features in the two languages.
Namespaces. A namespace provides a set of names that are grouped together, are usually logically related, and are
distinct from other namespaces. That enables a name foo in one namespace to have a distinct meaning from foo in
another namespace. A namespace is thus a scoping mechanism. Namespaces are essential for modularity. Without them,
the names that one programmer chooses could collide with the names another programmer chooses. In Java, classes (and
packages) group names. In OCaml, structures (which we will soon study) are similar to classes in that they group names —
but without any of the added complexity of object-oriented programming that usually accompanies classes (constructors,
static vs. instance members, inheritance, overriding, this, etc.) Structures are the core of the OCaml module system;
in fact, we’ve been using them all along without thinking too much about them.
Abstraction. An abstraction hides some information while revealing other information. Abstraction thus enables en-
capsulation, aka information hiding. Usually, abstraction mechanisms for modules allow revealing some names that exist
inside the module, but hiding some others. Abstractions therefore describe relationships among modules: there might be
many modules that could be considered to satisfy a given abstraction. Abstraction is essential for modularity, because it
enables implementers of a module to hide the details of the implementation from clients, thus preventing the clients from
abusing those details. In a large team, the modules one programmer designs are thereby protected from abuse by another
programmer. It also enables clients to be blissfully unaware of those details. So, in a large team, no programmer has to
be aware of all the details of all the modules. In Java, interfaces and abstract classes provide abstraction. In OCaml, sig-
natures are used to abstract structures by hiding some of the structure’s names and definitions. Signatures are essentially
the types of structures.
Code reuse. A module system enables code reuse by providing features that enable code from one module to be used as
part of another module without having to copy that code. Code reuse thereby enables programmers to build on the work
of others in a way that is maintainable: when the implementer of one module makes an improvement in that module, all
the programmers who are reusing that code automatically get the benefit of that improvement. Code reuse is essential
for modularity, because it enables “building blocks” that can be assembled and reassembled to form complex pieces of
software. In Java, subtyping and inheritance provide code reuse. In OCaml, functors and includes enable code reuse.
Functors are like functions, in that they produce new modules out of old modules. Includes are like an intelligent form of
copy-paste: they include code from one part of a program in another.

Warning: These analogies between Java and OCaml are necessarily imperfect. You might naturally come away
from the above discussion thinking either of the following:
• “Structures are like Java classes, and signatures are like interfaces.”
• “Structures are like Java objects, and signatures are like classes.”
Both are helpful to a degree, yet both are ultimately wrong. So it might be best to let go of object-oriented programming
at this point and come to terms with the OCaml module system in and of itself. Compared to Java, it’s just built
different.

162 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

7.2 Modules

We begin with a couple of examples of the OCaml module system before diving into the details.
A structure is simply a collection of definitions, such as:

struct
let inc x = x + 1
type primary_color = Red | Green | Blue
exception Oops
end

In a way, the structure is like a record: the structure has some distinct components with names. But unlike a record, it
can define new types, exceptions, and so forth.
By itself the code above won’t compile, because structures do not have the same first-class status as values like integers
or functions. You can’t just enter that code in utop, or pass that structure to a function, etc. What you can do is bind the
structure to a name:

module MyModule = struct


let inc x = x + 1
type primary_color = Red | Green | Blue
exception Oops
end

module MyModule :
sig
val inc : int -> int
type primary_color = Red | Green | Blue
exception Oops
end

The output from OCaml has the form:

module MyModule : sig ... end

This indicates that MyModule has been defined, and that it has been inferred to have the module type that appears to the
right of the colon. That module type is written as signature:

sig
val inc : int -> int
type primary_color = Red | Green | Blue
exception Oops
end

The signature itself is a collection of specifications. The specifications for variant types and exceptions are simply their
original definitions, so primary_color and Oops are no different than they were in the original structure. The
specification for inc though is written with the val keyword, exactly as the toplevel would respond if we defined inc
in it.

Note: This use of the word “specification” is perhaps confusing, since many programmers would use that word to mean
“the comments specifying the behavior of a function.” But if we broaden our sight a little, we could allow that the type
of a function is part of its specification. So it’s at least a related sense of the word.

7.2. Modules 163


OCaml Programming: Correct + Efficient + Beautiful

The definitions in a module are usually more closely related than those in MyModule. Often a module will implement
some data structure. For example, here is a module for stacks implemented as linked lists:

module ListStack = struct


(** [empty] is the empty stack. *)
let empty = []

(** [is_empty s] is whether [s] is empty. *)


let is_empty = function [] -> true | _ -> false

(** [push x s] pushes [x] onto the top of [s]. *)


let push x s = x :: s

(** [Empty] is raised when an operation cannot be applied


to an empty stack. *)
exception Empty

(** [peek s] is the top element of [s].


Raises [Empty] if [s] is empty. *)
let peek = function
| [] -> raise Empty
| x :: _ -> x

(** [pop s] is all but the top element of [s].


Raises [Empty] if [s] is empty. *)
let pop = function
| [] -> raise Empty
| _ :: s -> s
end

module ListStack :
sig
val empty : 'a list
val is_empty : 'a list -> bool
val push : 'a -> 'a list -> 'a list
exception Empty
val peek : 'a list -> 'a
val pop : 'a list -> 'a list
end

Important: The specification of pop might surprise you. Note that it does not return the top element. That’s the job of
peek. Instead, pop returns all but the top element.

We can then use that module to manipulate a stack:

ListStack.push 2 (ListStack.push 1 ListStack.empty)

- : int list = [2; 1]

Warning: There’s a common confusion lurking here for those programmers coming from object-oriented languages.
It’s tempting to think of ListStack as being an object on which you invoke methods. Indeed ListStack.push
vaguely looks like we’re invoking a push method on a ListStack object. But that’s not what is happening. In an

164 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

OO language you could instantiate many stack objects. But here, there is only one ListStack. Moreover it is not
an object, in large part because it has no notion of a this or self keyword to denote the receiving object of the
method call.

That’s admittedly rather verbose code. Soon we’ll see several solutions to that problem, but for now here’s one:

ListStack.(push 2 (push 1 empty))

- : int list = [2; 1]

By writing ListStack.(e), all the names from ListStack become usable in e without needing to write the prefix
ListStack. each time. Another improvement could be using the pipeline operator:

ListStack.(empty |> push 1 |> push 2)

- : int list = [2; 1]

Now we can read the code left-to-right without having to parse parentheses. Nice.

Warning: There’s another common OO confusion lurking here. It’s tempting to think of ListStack as being a
class from which objects are instantiated. That’s not the case though. Notice how there is no new operator used to
create a stack above, nor any constructors (in the OO sense of that word).

Modules are considerably more basic than classes. A module is just a collection of definitions in its own namespace. In
ListStack, we have some definitions of functions—push, pop, etc.—and one value, empty.
So whereas in Java we might create a couple of stacks using code like this:

Stack s1 = new Stack();


s1.push(1);
s1.push(2);
Stack s2 = new Stack();
s2.push(3);

In OCaml the same stacks could be created as follows:

let s1 = ListStack.(empty |> push 1 |> push 2)


let s2 = ListStack.(empty |> push 3)

val s1 : int list = [2; 1]

val s2 : int list = [3]

7.2. Modules 165


OCaml Programming: Correct + Efficient + Beautiful

7.2.1 Module Definitions

The module definition keyword is much like the let definition keyword that we learned before. (The OCaml designers
hypothetically could have chosen to use let_module instead of module to emphasize the similarity.) The difference
is just that:
• let binds a value to a name, whereas
• module binds a module value to a name.
Syntax.
The most common syntax for a module definition is simply:

module ModuleName = struct


module_items
end

where module_items inside a structure can include let definitions, type definitions, and exception definitions,
as well as nested module definitions. Module names must begin with an uppercase letter, and idiomatically they use
CamelCase rather than Snake_case.
But a more accurate version of the syntax would be:

module ModuleName = module_expression

where a struct is just one sort of module_expression. Here’s another: the name of an already defined module.
For example, you can write module L = List if you’d like a short alias for the List module. We’ll see other sorts
of module expressions later in this section and chapter.
The definitions inside a structure can optionally be terminated by ;; as in the toplevel:

module M = struct
let x = 0;;
type t = int;;
end

module M : sig val x : int type t = int end

Sometimes that can be useful to add temporarily if you are trying to diagnose a syntax error. It will help OCaml understand
that you want two definitions to be syntactically separate. After fixing whatever the underlying error is, though, you can
remove the ;;.
One use case for ;; is if you want to evaluate an expression as part of a module:

module M = struct
let x = 0;;
assert (x = 0);;
end

module M : sig val x : int end

But that can be rewritten without ;; as:

module M = struct
let x = 0
(continues on next page)

166 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


let _ = assert (x = 0)
end

module M : sig val x : int end

Structures can also be written on a single line, with optional ;; between items for readability:

module N = struct let x = 0 let y = 1 end


module O = struct let x = 0;; let y = 1 end

module N : sig val x : int val y : int end

module O : sig val x : int val y : int end

An empty structure is permitted:

module E = struct end

module E : sig end

Dynamic semantics.
We already know that expressions are evaluated to values. Similarly, a module expression is evaluated to a module value
or just “module” for short. The only interesting kind of module expression we have so far, from the perspective of
evaluation anyway, is the structure. Evaluation of structures is easy: just evaluate each definition in it, in the order they
occur. Because of that, earlier definitions are therefore in scope in later definitions, but not vice versa. So this module is
fine:

module M = struct
let x = 0
let y = x
end

module M : sig val x : int val y : int end

But this module is not, because at the time the let definition of x is being evaluated, y has not yet been bound:

module M = struct
let x = y
let y = 0
end

File "[13]", line 2, characters 10-11:


2 | let x = y
^
Error: Unbound value y

Of course, mutual recursion can be used if desired:

7.2. Modules 167


OCaml Programming: Correct + Efficient + Beautiful

module M = struct
(* Requires: input is non-negative. *)
let rec even = function
| 0 -> true
| n -> odd (n - 1)
and odd = function
| 0 -> false
| n -> even (n - 1)
end

module M : sig val even : int -> bool val odd : int -> bool end

Static semantics.
A structure is well-typed if all the definitions in it are themselves well-typed, according to all the typing rules we have
already learned.
As we’ve seen in toplevel output, the module type of a structure is a signature. There’s more to module types than that,
though. Let’s put that off for a moment to first talk about scope.

7.2.2 Scope and Open

After a module M has been defined, you can access the names within it using the dot operator. For example:

module M = struct let x = 42 end

module M : sig val x : int end

M.x

- : int = 42

Of course from outside the module the name x by itself is not meaningful:

File "[17]", line 1, characters 0-1:


1 | x
^
Error: Unbound value x

But you can bring all of the definitions of a module into the current scope using open:

open M

- : int = 42

168 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

Opening a module is like writing a local definition for each name defined in the module. For example, open String
brings all the definitions from the String module into scope, and has an effect similar to the following on the local names-
pace:

let length = String.length


let get = String.get
let lowercase_ascii = String.lowercase_ascii
...

If there are types, exceptions, or modules defined in a module, those also are brought into scope with open.
The Always-Open Module. There is a special module called Stdlib that is automatically opened in every OCaml
program. It contains the “built-in” functions and operators. You therefore never need to prefix any of the names it defines
with Stdlib., though you could do so if you ever needed to unambiguously identify a name from it. In earlier days,
this module was named Pervasives, and you might still see that name in some code bases.
Open as a Module Item. An open is another sort of module_item. So we can open one module inside another:

module M = struct
open List

(** [uppercase_all lst] upper-cases all the elements of [lst]. *)


let uppercase_all = map String.uppercase_ascii
end

module M : sig val uppercase_all : string list -> string list end

Since List is open, the name map from it is in scope. But what if we wanted to get rid of the String. as well?

module M = struct
open List
open String

(** [uppercase_all lst] upper-cases all the elements of [lst]. *)


let uppercase_all = map uppercase_ascii
end

File "[21]", line 6, characters 26-41:


6 | let uppercase_all = map uppercase_ascii
^^^^^^^^^^^^^^^
Error: This expression has type string -> string
but an expression was expected of type char -> char
Type string is not compatible with type char

Now we have a problem, because String also defines the name map, but with a different type than List. As usual a
later definition shadows an earlier one, so it’s String.map that gets chosen instead of List.map as we intended.
If you’re using many modules inside your code, chances are you’ll have at least one collision like this. Often it will be
with a standard higher-order function like map that is defined in many library modules.

Tip: It is therefore generally good practice not to open all the modules you’re going to use at the top of a .ml file or
structure. This is perhaps different than how you’re used to working with languages like Java, where you might import
many packages with *. Instead, it’s good to restrict the scope in which you open modules.

7.2. Modules 169


OCaml Programming: Correct + Efficient + Beautiful

Limiting the Scope of Open. We’ve already seen one way of limiting the scope of an open: M.(e). Inside e all the
names from module M are in scope. This is useful for briefly using M in a short expression:

(* remove surrounding whitespace from [s] and convert it to lower case *)


let s = "BigRed "
let s' = s |> String.trim |> String.lowercase_ascii (* long way *)
let s'' = String.(s |> trim |> lowercase_ascii) (* short way *)

val s : string = "BigRed "

val s' : string = "bigred"

But what if you want to bring a module into scope for an entire function, or some other large block of code? The
(admittedly strange) syntax for that is let open M in e. It makes all the names from M be in scope in e. For
example:

(** [lower_trim s] is [s] in lower case with whitespace removed. *)


let lower_trim s =
let open String in
s |> trim |> lowercase_ascii

val lower_trim : string -> string = <fun>

Going back to our uppercase_all example, it might be best to eschew any kind of opening and simply to be explicit
about which module we are using where:

module M = struct
(** [uppercase_all lst] upper-cases all the elements of [lst]. *)
let uppercase_all = List.map String.uppercase_ascii
end

module M : sig val uppercase_all : string list -> string list end

7.2.3 Module Type Definitions

We’ve already seen that OCaml will infer a signature as the type of a module. Let’s now see how to write those modules
types ourselves. As an example, here is a module type for our list-based stacks:

module type LIST_STACK = sig


exception Empty
val empty : 'a list
val is_empty : 'a list -> bool
val push : 'a -> 'a list -> 'a list
val peek : 'a list -> 'a
val pop : 'a list -> 'a list
end

module type LIST_STACK =


sig
exception Empty
(continues on next page)

170 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


val empty : 'a list
val is_empty : 'a list -> bool
val push : 'a -> 'a list -> 'a list
val peek : 'a list -> 'a
val pop : 'a list -> 'a list
end

Now that we have both a module and a module type for list-based stacks, we should move the specification comments
from the structure into the signature. Those comments are properly part of the specification of the names in the signature.
They specify behavior, thus augmenting the specification of types provided by the val declarations.

module type LIST_STACK = sig


(** [Empty] is raised when an operation cannot be applied
to an empty stack. *)
exception Empty

(** [empty] is the empty stack. *)


val empty : 'a list

(** [is_empty s] is whether [s] is empty. *)


val is_empty : 'a list -> bool

(** [push x s] pushes [x] onto the top of [s]. *)


val push : 'a -> 'a list -> 'a list

(** [peek s] is the top element of [s].


Raises [Empty] if [s] is empty. *)
val peek : 'a list -> 'a

(** [pop s] is all but the top element of [s].


Raises [Empty] if [s] is empty. *)
val pop : 'a list -> 'a list
end

module ListStack = struct


let empty = []

let is_empty = function [] -> true | _ -> false

let push x s = x :: s

exception Empty

let peek = function


| [] -> raise Empty
| x :: _ -> x

let pop = function


| [] -> raise Empty
| _ :: s -> s
end

Nothing so far, however, tells OCaml that there is a relationship between LIST_STACK and ListStack. If we want
OCaml to ensure that ListStack really does have the module type specified by LIST_STACK, we can add a type
annotation in the first line of the module definition:

7.2. Modules 171


OCaml Programming: Correct + Efficient + Beautiful

module ListStack : LIST_STACK = struct


let empty = []

let is_empty = function [] -> true | _ -> false

let push x s = x :: s

exception Empty

let peek = function


| [] -> raise Empty
| x :: _ -> x

let pop = function


| [] -> raise Empty
| _ :: s -> s
end

module ListStack : LIST_STACK

The compiler agrees that the module ListStack does define all the items specified by LIST_STACK with appropriate
types. If we had accidentally omitted some item, the type annotation would have been rejected:

module ListStack : LIST_STACK = struct


let empty = []

let is_empty = function [] -> true | _ -> false

let push x s = x :: s

exception Empty

let peek = function


| [] -> raise Empty
| x :: _ -> x

(* [pop] is missing *)
end

File "[28]", lines 1-15, characters 32-3:


1 | ................................struct
2 | let empty = []
3 |
4 | let is_empty = function [] -> true | _ -> false
5 |
...
12 | | x :: _ -> x
13 |
14 | (* [pop] is missing *)
15 | end
Error: Signature mismatch:
...
The value `pop' is required but not provided
File "[26]", line 21, characters 2-30: Expected declaration

Syntax.

172 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

The most common syntax for a module type is simply:

module type ModuleTypeName = sig


specifications
end

where specifications inside a signature can include val declarations, type definitions, exception definitions, and
nested module type definitions. Like structures, a signature can be written on many lines or just one line, and the
empty signature sig end is allowed.
But, as we saw with module definitions, a more accurate version of the syntax would be:

module type ModuleTypeName = module_type

where a signature is just one sort of module_type. Another would be the name of an already defined module type—
e.g., module type LS = LIST_STACK. We’ll see other module types later in this section and chapter.
By convention, module type names are usually CamelCase, like module names. So why did we use ALL_CAPS above
for LIST_STACK? It was to avoid a possible point of confusion in that example, which we now illustrate. We could
instead have used ListStack as the name of both the module and the module type:

module type ListStack = sig ... end


module ListStack : ListStack = struct ... end

In OCaml the namespaces for modules and module types are distinct, so it’s perfectly valid to have a module named
ListStack and a module type named ListStack. The compiler will not get confused about which you mean, be-
cause they occur in distinct syntactic contexts. But as a human you might well get confused by those seemingly overloaded
names.

Note: The use of ALL_CAPS for module types was at one point common, and you might see it still. It’s an older
convention from Standard ML. But the social conventions of all caps have changed since those days. To modern readers,
a name like LIST_STACK might feel like your code is impolitely shouting at you. That is a connotation that evolved in
the 1980s. Older programming languages (e.g., Pascal, COBOL, FORTRAN) commonly used all caps for keywords and
even their own names. Modern languages still idiomatically use all caps for constants—see, for example, Java’s Math.PI
or Python’s style guide.

More Syntax.
We should also add syntax now for module type annotations. Module definitions may include an optional type annotation:

module ModuleName : module_type = module_expression

And module expressions may include manual type annotations:

(module_expression : module_type)

That syntax is analogous to how we can write (e : t) to manually specify the type t of an expression e.
Here are a few examples to show how that syntax can be used:

module ListStackAlias : LIST_STACK = ListStack


(* equivalently *)
module ListStackAlias = (ListStack : LIST_STACK)

(continues on next page)

7.2. Modules 173


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


module M : sig val x : int end = struct let x = 42 end
(* equivalently *)
module M = (struct let x = 42 end : sig val x : int end)

And, module types can include nested module specifications:

module type X = sig


val x : int
end

module type T = sig


module Inner : X
end

module M : T = struct
module Inner : X = struct
let x = 42
end
end

In the example above, T specifies that there must be an inner module named Inner whose module type is X. Here, the
type annotation is mandatory, because otherwise nothing would be known about Inner. In implementing T, module M
therefore has to provide a module (i) with that name, which also (ii) meets the specifications of module type X.
Dynamic semantics.
Since module types are in fact types, they are not evaluated. They have no dynamic semantics.
Static semantics.
Earlier in this section we delayed discussing the static semantics of module expressions. Now that we have learned about
module types, we can return to that discussion. We do so, next, in its own section, because the discussion will be lengthy.

7.2.4 Module Type Semantics

If M is just a struct block, its module type is whatever signature the compiler infers for it. But that can be changed by
module type annotations. The key question we have to answer is: what does a type annotation mean for modules? That
is, what does it mean when we write the : T in module M : T = ...?
There are two properties the compiler guarantees:
1. Signature matching: every name declared in T is defined in M at the same or a more general type.
2. Opacity: any name defined in M that does not appear in T is not visible to code outside of M.
But a more complete answer turns out to involve subtyping, which is a concept you’ve probably seen before in an object-
oriented language. We’re going to take a brief detour into that realm now, then come back to OCaml and modules.
In Java, the extends keyword creates subtype relationships between classes:

class C { }
class D extends C { }

D d = new D();
C c = d;

174 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

Subtyping is what permits the assignment of d to c on the last line of that example. Because D extends C, Java considers D
to be a subtype of C, and therefore permits an object instantiated from D to be used any place where an object instantiated
from C is expected. It’s up to the programmer of D to ensure that doesn’t lead to any run-time errors, of course. The
methods of D have to ensure that class invariants of C hold, for example. So by writing D extends C, the programmer
is taking on some responsibility, and in turn gaining some flexibility by being able to write such assignment statements.
So what is a “subtype”? That notion is in many ways dependent on the language. For a language-independent notion,
we turn to Barbara Liskov. She won the Turing Award in 2008 in part for her work on object-oriented language design.
Twenty years before that, she invented what is now called the Liskov Substitution Principle to explain subtyping. It says
that if S is a subtype of T, then substituting an object of type S for an object of type T should not change any desirable
behaviors of a program. You can see that at work in the Java example above, both in terms of what the language allows
and what the programmer must guarantee.
The particular flavor of subtyping in Java is called nominal subtyping, which is to say, it is based on names. In our example,
D is a subtype of C just because of the way the names were declared. The programmer decreed that subtype relationship,
and the language accepted the decree without question. Indeed, the only subtype relationships that exist are those that
have been decreed by name through such uses of extends and implements.
Now it’s time to return to OCaml. Its module system also uses subtyping, with the same underlying intuition about the
Liskov Substitution Principle. But OCaml uses a different flavor called structural subtyping. That is, it is based on the
structure of modules rather than their names. “Structure” here simply means the definitions contained in the module.
Those definitions are used to determine whether (M : T) is acceptable as a type annotation, where M is a module and
T is a module type.
Let’s play with this idea of structure through several examples, starting with this module:

module M = struct
let x = 0
let z = 2
end

module M : sig val x : int val z : int end

Module M contains two definitions. You can see those in the signature for the module that OCaml outputs: it contains x
: int and z : int. Because of the former, the module type annotation below is accepted:

module type X = sig


val x : int
end

module MX = (M : X)

module type X = sig val x : int end

module MX : X

Module type X requires a module item named x with type int. Module M does contain such an item. So (M : X) is
valid. The same would work for z:

module type Z = sig


val z : int
end

module MZ = (M : Z)

7.2. Modules 175


OCaml Programming: Correct + Efficient + Beautiful

module type Z = sig val z : int end

module MZ : Z

Or for both x and z:

module type XZ = sig


val x : int
val z : int
end

module MXZ = (M : XZ)

module type XZ = sig val x : int val z : int end

module MXZ : XZ

But not for y, because M contains no such item:

module type Y = sig


val y : int
end

module MY = (M : Y)

module type Y = sig val y : int end

File "[35]", line 5, characters 13-14:


5 | module MY = (M : Y)
^
Error: Signature mismatch:
Modules do not match:
sig val x : int val z : int end
is not included in
Y
The value `y' is required but not provided
File "[35]", line 2, characters 2-13: Expected declaration

Take a close look at that error message. Learning to read such errors on small examples will help you when they appear
in large bodies of code. OCaml is comparing two signatures, corresponding to the two expressions on either side of the
colon in (M : Y). The line

sig val x : int val z : int end

is the signature that OCaml is using for M. Since M is a module, that signature is just the names and types as they were
defined in M. OCaml compares that signature to Y, and discovers a mismatch:

The value `y' is required but not provided

That’s because Y requires y but M provides no such definition.


Here’s another error message to practice reading:

176 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

module type Xstring = sig


val x : string
end

module MXstring = (M : Xstring)

module type Xstring = sig val x : string end

File "[36]", line 5, characters 19-20:


5 | module MXstring = (M : Xstring)
^
Error: Signature mismatch:
Modules do not match:
sig val x : int val z : int end
is not included in
Xstring
Values do not match: val x : int is not included in val x : string
The type int is not compatible with the type string
File "[36]", line 2, characters 2-16: Expected declaration
File "[31]", line 2, characters 6-7: Actual declaration

This time the error is

Values do not match: val x : int is not included in val x : string

The error changed, because M does provide a definition of x, but at a different type than Xstring requires. That’s what
“is not included in” means here. So why doesn’t OCaml say something a little more straightforward, like “is not the same
as”? It’s because the types do not have to be exactly the same. If the provided value’s type is polymorphic, it suffices for
the required value’s type to be an instantiation of that polymorphic type.
For example, if a signature requires a type int -> int, it suffices for a structure to provide a value of type 'a ->
'a:

module type IntFun = sig


val f : int -> int
end

module IdFun = struct


let f x = x
end

module Iid = (IdFun : IntFun)

module type IntFun = sig val f : int -> int end

module IdFun : sig val f : 'a -> 'a end

module Iid : IntFun

So far all these examples were just a matter of comparing the definitions required by a signature to the definitions provided
by a structure. But here’s an example that might be surprising:

7.2. Modules 177


OCaml Programming: Correct + Efficient + Beautiful

module MXZ' = ((M : X) : Z)

File "[38]", line 1, characters 15-22:


1 | module MXZ' = ((M : X) : Z)
^^^^^^^
Error: Signature mismatch:
Modules do not match: X is not included in Z
The value `z' is required but not provided
File "[33]", line 2, characters 2-13: Expected declaration

Why does OCaml complain that z is required but not provided? We know from the definition of M that it indeed does
have a value z : int. Yet the error message perhaps strangely claims:

The value `z' is required but not provided.

The reason for this error is that we’ve already supplied the type annotation X in the module expression (M : X). That
causes the module expression to be known only at the module type X. In other words, we’ve forgotten irrevocably about
the existence of z after that annotation. All that is known is that the module has items required by X.
After all those examples, here are the static semantics of module type annotations:
• Module type annotation (M : T) is valid if the module type of M is a subtype of T. The module type of (M :
T) is then T in any further type checking.
• Module type S is a subtype of T if the set of definitions in S is a superset of those in T. Definitions in T are permitted
to instantiate type variables from S.
The “sub” vs. “super” in the second rule is not a typo. Consider these module types and modules:

module type T = sig


val a : int
end

module type S = sig


val a : int
val b : bool
end

module A = struct
let a = 0
end

module AB = struct
let a = 0
let b = true
end

module AC = struct
let a = 0
let c = 'c'
end

module type T = sig val a : int end

module type S = sig val a : int val b : bool end

178 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

module A : sig val a : int end

module AB : sig val a : int val b : bool end

module AC : sig val a : int val c : char end

Module type S provides a superset of the definitions in T, because it adds a definition of b. So why is S called a subtype
of T? Think about the set Type(𝑇 ) of all module values M such that M : T. That set contains A, AB, AC, and many
others. Also think about the set Type(𝑆) of all module values M such that M : S. That set contains AB but not A nor
AC. So Type(𝑆) ⊂ Type(𝑇 ), because there are some module values that are in Type(𝑇 ) but not in Type(𝑆).
As another example, a module type StackHistory for stacks might customize our usual Stack signature by adding
an operation history : 'a t -> int to return how many items have ever been pushed on the stack in its history.
That history operation makes the set of definitions in StackHistory bigger than the set in Stack, hence the use
of “superset” in the rule above. But the set of module values that implement StackHistory is smaller than the set of
module values that implement Stack, hence the use of “subset”.

7.2.5 Module Types are Static

Decisions about validity of module type annotations are made at compile time rather than run time.

Important: Module type annotations therefore offer potential confusion to programmers accustomed to object-oriented
languages, in which subtyping works differently.

Python programmers, for example, are accustomed to so-called “duck typing”. They might expect ((M : X) : Z)
to be valid, because z does exist at run-time in M. But in OCaml, the compile-time type of (M : X) has hidden z from
view irrevocably.
Java programmers, on the other hand, might expect that module type annotations work like type casts. So it might seem
valid to first “cast” M to X then to Z. In Java such type casts are checked, as needed, at run time. But OCaml module type
annotations are static. Once an annotation of X is made, there is no way to check at compile time what other items might
exist in the module—that would require a run-time check, which OCaml does not permit.
In both cases it might feel as though OCaml is being too restrictive. Maybe. But in return for that restrictiveness, OCaml
is guaranteeing an absence of run-time errors of the kind that would occur in Java or Python, whether because of a
run-time error from a cast, or a run-time error from a missing method.

7.2.6 First-Class Modules

Modules are not as first-class in OCaml as functions. But it is possible to package modules as first-class values. Briefly:
• (module M : T) packages module M with module type T into a value.
• (val e : T) un-packages e into a module with type T.
We won’t cover this much further, but if you’re curious you can have a look at the manual.

7.2. Modules 179


OCaml Programming: Correct + Efficient + Beautiful

7.3 Modules and the Toplevel

Note: The video below uses the legacy build system, ocamlbuild, rather than the new build system, dune. Some of the
details change with dune, as described in the text below.

There are several pragmatics involving modules and the toplevel that are important to master to use the two together
effectively.

7.3.1 Loading Compiled Modules

Compiling an OCaml file produces a module having the same name as the file, but with the first letter capitalized. These
compiled modules can be loaded into the toplevel using #load.
For example, suppose you create a file called mods.ml, and put the following code in it:

let b = "bigred"
let inc x = x + 1
module M = struct
let y = 42
end

Note that there is no module Mods = struct ... end around that. The code is at the topmost level of the file,
as it were.
Then suppose you type ocamlc mods.ml to compile it. One of the newly-created files is mods.cmo: this is a
compiled module object file, aka bytecode.
You can make this bytecode available for use in the toplevel with the following directives. Recall that the # character is
required in front of a directive. It is not part of the prompt.

# #load "mods.cmo";;

That directive loads the bytecode found in mods.cmo, thus making a module named Mods available to be used. It is
exactly as if you had entered this code:

module Mods = struct


let b = "bigred"
let inc x = x + 1
module M = struct
let y = 42
end
end

module Mods :
sig val b : string val inc : int -> int module M : sig val y : int end end

Both of these expressions will therefore evaluate successfully:

Mods.b;;
Mods.M.y;;

180 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

- : string = "bigred"

- : int = 42

But this will fail:

inc

File "[3]", line 1, characters 0-3:


1 | inc
^^^
Error: Unbound value inc
Hint: Did you mean incr?

It fails because inc is in the namespace of Mods.

Mods.inc

- : int -> int = <fun>

Of course, if you open the module, you can directly name inc:

open Mods;;
inc;;

- : int -> int = <fun>

7.3.2 Dune

Dune provides a command to make it easier to start utop with libraries already loaded. Suppose we add this dune file to
the same directory as mods.ml:

(library
(name mods))

That tells dune to build a library named Mods out of mods.ml (and any other files in the same directory, if they existed).
Then we can run this command to launch utop with that library already loaded:

$ dune utop

Now right away we can access components of Mods without having to issue a #load directive:

Mods.inc

- : int -> int = <fun>

The dune utop command accepts a directory name as an argument if you want to load libraries in a particular subdi-
rectory of your source code.

7.3. Modules and the Toplevel 181


OCaml Programming: Correct + Efficient + Beautiful

7.3.3 Initializing the Toplevel

If you are doing a lot of testing of a particular module, it can be annoying to have to type directives every time you start
utop. You really want to initialize the toplevel with some code as it launches, so that you don’t have to keep typing that
code.
The solution is to create a file in the working directory and call that file .ocamlinit. Note that the . at the front of
that filename is required and makes it a hidden file that won’t appear in directory listings unless explicitly requested (e.g.,
with ls -a). Everything in .ocamlinit will be processed by utop when it loads.
For example, suppose you create a file named .ocamlinit in the same directory as mods.ml, and in that file put the
following code:

open Mods;;

Now restart utop with dune utop. All the names defined in Mods will already be in scope. For example, these will
both succeed:

inc;;
M.y;;

- : int -> int = <fun>

- : int = 42

7.3.4 Requiring Libraries

Suppose you wanted to experiment with some OUnit code in utop. You can’t actually open it:

open OUnit2;;

File "[8]", line 1, characters 5-11:


1 | open OUnit2;;
^^^^^^
Error: Unbound module OUnit2
Hint: Did you mean Unit?

The problem is that the OUnit library hasn’t been loaded into utop yet. It can be with the following directive:

#require "ounit2";;

Now you can successfully load your own module without getting an error.

open OUnit2;;

182 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

7.3.5 Load vs Use

There is a big difference between #load-ing a compiled module file and #use-ing an uncompiled source file. The
former loads bytecode and makes it available for use. For example, loading mods.cmo caused the Mod module to be
available, and we could access its members with expressions like Mod.b. The latter (#use) is textual inclusion: it’s like
typing the contents of the file directly into the toplevel. So using mods.ml does not cause a Mod module to be available,
and the definitions in the file can be accessed directly, e.g., b.
For example, in the following interaction, we can directly refer to b but cannot use the qualified name Mods.b:

# #use "mods.ml"

# b;;
val b : string = "bigred"

# Mods.b;;
Error: Unbound module Mods

Whereas in this interaction the situation is reversed:

# #directory "_build";;
# #load "mods.cmo";;

# Mods.b;;
- : string = "bigred"

# b;;
Error: Unbound value b

So when you’re using the toplevel to experiment with your code, it’s often better to work with #load rather than #use.
The #load directive accurately reflects how your modules interact with each other and with the outside world.

7.4 Encapsulation

One of the main concerns of a module system is to provide encapsulation: the hiding of information about implementation
behind an interface. OCaml’s module system makes this possible with a feature we’ve already seen: the opacity that module
type annotations create. One special use of opacity is the declaration of abstract types. We’ll study both of those ideas in
this section.

7.4.1 Opacity

When implementing a module, you might sometimes have helper functions that you don’t want to expose to clients of the
module. For example, maybe you’re implementing a math module that provides a tail-recursive factorial function:

module Math = struct


(** [fact_aux n acc] is [n! * acc]. *)
let rec fact_aux n acc =
if n = 0 then acc else fact_aux (n - 1) (n * acc)

(** [fact n] is [n!]. *)


let fact n = fact_aux n 1
end

7.4. Encapsulation 183


OCaml Programming: Correct + Efficient + Beautiful

module Math : sig val fact_aux : int -> int -> int val fact : int -> int end

You’d like to make fact usable by clients of Math, but you’d also like to keep fact_aux hidden. But in the code
above, you can see that fact_aux is visible in the signature inferred for Math. One way to hide it is simply to nest
fact_aux:

module Math = struct


(** [fact n] is [n!]. *)
let fact n =
(** [fact_aux n acc] is [n! * acc]. *)
let rec fact_aux n acc =
if n = 0 then acc else fact_aux (n - 1) (n * acc)
in
fact_aux n 1
end

module Math : sig val fact : int -> int end

Look at the signature, and notice how fact_aux is gone. But, that nesting makes fact just a little harder to read. It
also means fact_aux is not available for any other functions inside Math to use. In this case that’s probably fine—there
probably aren’t any other functions in Math that need fact_aux. But if there were, we couldn’t nest fact_aux.
So another way to hide fact_aux from clients of Math, while still leaving it available for implementers of Math, is to
use a module type that exposes only those names that clients should see:

module type MATH = sig


(** [fact n] is [n!]. *)
val fact : int -> int
end

module Math : MATH = struct


(** [fact_aux n acc] is [n! * acc]. *)
let rec fact_aux n acc =
if n = 0 then acc else fact_aux (n - 1) (n * acc)

let fact n = fact_aux n 1


end

module type MATH = sig val fact : int -> int end

module Math : MATH

Now since MATH does not mention fact_aux, the module type annotation Math : MATH causes fact_aux to be
hidden:

Math.fact_aux

File "[4]", line 1, characters 0-13:


1 | Math.fact_aux
^^^^^^^^^^^^^
Error: Unbound value Math.fact_aux

In that sense, module type annotations are opaque: they can prevent visibility of module items. We say that the module
type seals the module, making any components not named in the module type be inaccessible.

184 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

Important: Remember that module type annotations are therefore not only about checking to see whether a module
defines certain items. The annotations also hide items.

What if you did want to just check the definitions, but not hide anything? Then don’t supply the annotation at the time of
module definition:

module type MATH = sig


(** [fact n] is [n!]. *)
val fact : int -> int
end

module Math = struct


(** [fact_aux n acc] is [n! * acc]. *)
let rec fact_aux n acc =
if n = 0 then acc else fact_aux (n - 1) (n * acc)

let fact n = fact_aux n 1


end

module MathCheck : MATH = Math

module type MATH = sig val fact : int -> int end

module Math : sig val fact_aux : int -> int -> int val fact : int -> int end

module MathCheck : MATH

Now Math.fact_aux is visible, but MathCheck.fact_aux is not:

Math.fact_aux

- : int -> int -> int = <fun>

MathCheck.fact_aux

File "[7]", line 1, characters 0-18:


1 | MathCheck.fact_aux
^^^^^^^^^^^^^^^^^^
Error: Unbound value MathCheck.fact_aux

You wouldn’t even have to give the “check” module a name since you probably never intend to access it; you could instead
leave it anonymous:

module _ : MATH = Math

A Comparison to Visibility Modifiers. The use of sealing in OCaml is thus similar to the use of visibility modifiers
such as private and public in Java. In fact one way to think about Java class definitions is that they simultaneously
define multiple signatures.
For example, consider this Java class:

7.4. Encapsulation 185


OCaml Programming: Correct + Efficient + Beautiful

class C {
private int x;
public int y;
}

An analogy to it in OCaml would be the following modules and types:

module type C_PUBLIC = sig


val y : int
end

module CPrivate = struct


let x = 0
let y = 0
end

module C : C_PUBLIC = CPrivate

module type C_PUBLIC = sig val y : int end

module CPrivate : sig val x : int val y : int end

module C : C_PUBLIC

With those definitions, any code that uses C will have access only to the names exposed in the C_PUBLIC module type.
That analogy can be extended to the other visibility modifiers, protected and default, as well. Which means that Java
classes are effectively defining four related types, and the compiler is making sure the right type is used at each place in
the code base C is named. No wonder it can be challenging to master visibility in OO languages at first.

7.4.2 Abstract Types

In an earlier section we implemented stacks as lists with the following module and type:

module type LIST_STACK = sig


(** [Empty] is raised when an operation cannot be applied
to an empty stack. *)
exception Empty

(** [empty] is the empty stack. *)


val empty : 'a list

(** [is_empty s] is whether [s] is empty. *)


val is_empty : 'a list -> bool

(** [push x s] pushes [x] onto the top of [s]. *)


val push : 'a -> 'a list -> 'a list

(** [peek s] is the top element of [s].


Raises [Empty] if [s] is empty. *)
val peek : 'a list -> 'a

(** [pop s] is all but the top element of [s].


(continues on next page)

186 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


Raises [Empty] if [s] is empty. *)
val pop : 'a list -> 'a list
end

module ListStack : LIST_STACK = struct


exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
end

What if we wanted to modify that data structure to add an operation for the size of the stack? The easy way would be to
implement it using List.length:

module type LIST_STACK = sig


...
(** [size s] is the number of elements on the stack. *)
val size : 'a list -> int
end

module ListStack : LIST_STACK = struct


...
let size = List.length
end

That results in a linear-time implementation of size. What if we wanted a faster, constant-time implementation? At
the cost of a little space, we could cache the size of the stack. Let’s now represent the stack as a pair, where the first
component of the pair is the same list as before, and the second component of the pair is the size of the stack:

module ListStackCachedSize = struct


exception Empty
let empty = ([], 0)
let is_empty = function ([], _) -> true | _ -> false
let push x (stack, size) = (x :: stack, size + 1)
let peek = function ([], _) -> raise Empty | (x :: _, _) -> x
let pop = function
| ([], _) -> raise Empty
| (_ :: stack, size) -> (stack, size - 1)
end

We have a big problem. ListStackCachedSize does not implement the LIST_STACK module type, because that
module type specifies 'a list throughout it to represent the stack—not 'a list * int.

module CheckListStackCachedSize : LIST_STACK = ListStackCachedSize

File "[12]", line 1, characters 47-66:


1 | module CheckListStackCachedSize : LIST_STACK = ListStackCachedSize
^^^^^^^^^^^^^^^^^^^
Error: Signature mismatch:
...
Values do not match:
val empty : 'a list * int
(continues on next page)

7.4. Encapsulation 187


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


is not included in
val empty : 'a list
The type 'a list * int is not compatible with the type 'b list
File "[10]", line 7, characters 2-21: Expected declaration
File "[11]", line 3, characters 6-11: Actual declaration

Moreover, any code we previously wrote using ListStack now has to be modified to deal with the pair, which could
mean revising pattern matches, function types, and so forth.
As you no doubt learned in earlier programming courses, the problem we are encountering here is a lack of encapsulation.
We should have kept the type that implements ListStack hidden from clients. In Java, for example, we might have
written:

class ListStack<T> {
private List<T> stack;
private int size;
...
}

That way clients of ListStack would be unaware of stack or size. In fact, they wouldn’t be able to name those
fields at all. Instead, they would just use ListStack as the type of the stack:

ListStack<Integer> s = new ListStack<>();


s.push(1);

So in OCaml, how can we keep the representation type of the stack hidden? What we learned about opacity and seal-
ing thus far does not suffice. The problem is that the type 'a list * int literally appears in the signature of
ListStackCachedSize, e.g., in push:

ListStackCachedSize.push

- : 'a -> 'a list * int -> 'a list * int = <fun>

A module type annotation could hide one of the values defined in ListStackCachedSize, e.g., push itself, but
that doesn’t solve the problem: we need to hide the type 'a list * int while exposing the operation push. So
OCaml has a feature for doing exactly that: abstract types. Let’s see an example of this feature.
We begin by modifying LIST_STACK, replacing 'a list with a new type 'a stack everywhere. We won’t repeat
the specification comments here, so as to keep the example shorter. And while we’re at it, let’s add the size operation.

module type LIST_STACK = sig


type 'a stack
exception Empty
val empty : 'a stack
val is_empty : 'a stack -> bool
val push : 'a -> 'a stack -> 'a stack
val peek : 'a stack -> 'a
val pop : 'a stack -> 'a stack
val size : 'a stack -> int
end

Note how 'a stack is not actually defined in that signature. We haven’t said anything about what it is. It might be 'a
list, or 'a list * int, or {stack : 'a list; size : int}, or anything else. That is what makes it
an abstract type: we’ve declared its name but not specified its definition.

188 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

Now ListStackCachedSize can implement that module type with the addition of just one line of code: the first
line of the structure, which defines 'a stack:

module ListStackCachedSize : LIST_STACK = struct


type 'a stack = 'a list * int
exception Empty
let empty = ([], 0)
let is_empty = function ([], _) -> true | _ -> false
let push x (stack, size) = (x :: stack, size + 1)
let peek = function ([], _) -> raise Empty | (x :: _, _) -> x
let pop = function
| ([], _) -> raise Empty
| (_ :: stack, size) -> (stack, size - 1)
let size = snd
end

module ListStackCachedSize : LIST_STACK

Take a careful look at the output: nowhere does 'a list show up in it. In fact, only LIST_STACK does. And
LIST_STACK mentions only 'a stack. So no one’s going to know that internally a list is used. (Ok, they’re going to
know: the name suggests it. But the point is they can’t take advantage of that, because the type is abstract.)
Likewise, our original implementation with linear-time size satisfies the module type. We just have to add a line to
define 'a stack:

module ListStack : LIST_STACK = struct


type 'a stack = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
end

module ListStack : LIST_STACK

Note that omitting that added line would result in an error, just as if we had failed to define push or any of the other
operations from the module type:

module ListStack : LIST_STACK = struct


(* type 'a stack = 'a list *)
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
end

File "[17]", lines 1-10, characters 32-3:


1 | ................................struct
(continues on next page)

7.4. Encapsulation 189


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


2 | (* type 'a stack = 'a list *)
3 | exception Empty
4 | let empty = []
5 | let is_empty = function [] -> true | _ -> false
6 | let push x s = x :: s
7 | let peek = function [] -> raise Empty | x :: _ -> x
8 | let pop = function [] -> raise Empty | _ :: s -> s
9 | let size = List.length
10 | end
Error: Signature mismatch:
...
The type `stack' is required but not provided
File "[14]", line 2, characters 2-15: Expected declaration

Here is a third, custom implementation of LIST_STACK. This one is deliberately overly-complicated, in part to illustrate
how the abstract type can hide implementation details that are better not revealed to clients:

module CustomStack : LIST_STACK = struct


type 'a entry = {top : 'a; rest : 'a stack; size : int}
and 'a stack = S of 'a entry option
exception Empty
let empty = S None
let is_empty = function S None -> true | _ -> false
let size = function S None -> 0 | S (Some {size}) -> size
let push x s = S (Some {top = x; rest = s; size = size s + 1})
let peek = function S None -> raise Empty | S (Some {top}) -> top
let pop = function S None -> raise Empty | S (Some {rest}) -> rest
end

module CustomStack : LIST_STACK

Is that really a “list” stack? It satisfies the module type LIST_STACK. But upon reflection, that module type never really
had anything to do with lists once we made the type 'a stack abstract. There’s really no need to call it LIST_STACK.
We’d be better off using just STACK, since it can be implemented with list or without. At that point, we could just
go with Stack as its name, since there is no module named Stack we’ve written that would be confused with it. That
avoids the all-caps look of our code shouting at us.

module type Stack = sig


type 'a stack
exception Empty
val empty : 'a stack
val is_empty : 'a stack -> bool
val push : 'a -> 'a stack -> 'a stack
val peek : 'a stack -> 'a
val pop : 'a stack -> 'a stack
val size : 'a stack -> int
end

module ListStack : Stack = struct


type 'a stack = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
(continues on next page)

190 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
end

There’s one further naming improvement we could make. Notice the type of ListStack.empty (and don’t worry
about the abstr part for now; we’ll come back to it):

ListStack.empty

- : 'a ListStack.stack = <abstr>

That type, 'a ListStack.stack, is rather unwieldy, because it conveys the word “stack” twice: once in the name of
the module, and again in the name of the representation type inside that module. In places like this, OCaml programmers
idiomatically use a standard name, t, in place of a longer representation type name:

module type Stack = sig


type 'a t
exception Empty
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
val peek : 'a t -> 'a
val pop : 'a t -> 'a t
val size : 'a t -> int
end

module ListStack : Stack = struct


type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
end

module CustomStack : Stack = struct


type 'a entry = {top : 'a; rest : 'a t; size : int}
and 'a t = S of 'a entry option
exception Empty
let empty = S None
let is_empty = function S None -> true | _ -> false
let size = function S None -> 0 | S (Some {size}) -> size
let push x s = S (Some {top = x; rest = s; size = size s + 1})
let peek = function S None -> raise Empty | S (Some {top}) -> top
let pop = function S None -> raise Empty | S (Some {rest}) -> rest
end

Now the type of stacks is simpler:

ListStack.empty;;
CustomStack.empty;;

7.4. Encapsulation 191


OCaml Programming: Correct + Efficient + Beautiful

- : 'a ListStack.t = <abstr>

- : 'a CustomStack.t = <abstr>

That idiom is fairly common when there’s a single representation type exposed by an interface to a data structure. You’ll
see it used throughout the standard library.
In informal conversation we would usually pronounce those types without the “dot t” part. For example, we might say
“alpha ListStack”, simply ignoring the t—though it does technically have to be there to be legal OCaml code.
Finally, abstract types are really just a special case of opacity. You actually can expose the definition of a type in a
signature if you want to:

module type T = sig


type t = int
val x : t
end

module M : T = struct
type t = int
let x = 42
end

let a : int = M.x

module type T = sig type t = int val x : t end

module M : T

val a : int = 42

Note how we’re able to use M.x at its type of int. That works because the equality of types t and int has been exposed
in the module type. But if we kept t abstract, the same usage would fail:

module type T = sig


type t (* = int *)
val x : t
end

module M : T = struct
type t = int
let x = 42
end

let a : int = M.x

module type T = sig type t val x : t end

module M : T

192 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

File "[24]", line 11, characters 14-17:


11 | let a : int = M.x
^^^
Error: This expression has type M.t but an expression was expected of type
int

We’re not allowed to use M.x at type int outside of M, because its type M.t is abstract. This is encapsulation at work,
keeping that implementation detail hidden.

7.4.3 Pretty Printing

In some output above, we observed something curious: the toplevel prints <abstr> in place of the actual contents of a
value whose type is abstract:

ListStack.empty;;
ListStack.(empty |> push 1 |> push 2);;

- : 'a ListStack.t = <abstr>

- : int ListStack.t = <abstr>

Recall that the toplevel uses this angle-bracket convention to indicate an unprintable value. We’ve encountered that before
with functions and <fun>:

fun x -> x

- : 'a -> 'a = <fun>

On the one hand, it’s reasonable for the toplevel to behave this way. Once a type is abstract, its implementation isn’t
meant to be revealed to clients. So actually printing out the list [] or [2; 1] as responses to the above inputs would be
revealing more than is intended.
On the other hand, it’s also reasonable for implementers to provide clients with a friendly way to view a value of an abstract
type. Java programmers, for example, will often write toString() methods so that objects can be printed as output
in the terminal or in JShell. To support that, the OCaml toplevel has a directive #install_printer, which registers
a function to print values. Here’s how it works.
• You write a pretty printing function of type Format.formatter -> t -> unit, for whatever type t you
like. Let’s suppose for sake of example that you name that function pp.
• You invoke #install_printer pp in the toplevel.
• From now on, anytime the toplevel wants to print a value of type t it uses your function pp to do so.
It probably makes sense the pretty printing function needs to take in a value of type t (because that’s what it needs to
print) and returns unit (as other printing functions do). But why does it take the Format.formatter argument?
It’s because of a fairly high-powered feature that OCaml is attempting to provide here: automatic line breaking and
indentation in the middle of very large outputs.
Consider the output from this expression, which creates nested lists:

List.init 15 (fun n -> List.init n (Fun.const n))

7.4. Encapsulation 193


OCaml Programming: Correct + Efficient + Beautiful

- : int list list =


[[]; [1]; [2; 2]; [3; 3; 3]; [4; 4; 4; 4]; [5; 5; 5; 5; 5];
[6; 6; 6; 6; 6; 6]; [7; 7; 7; 7; 7; 7; 7]; [8; 8; 8; 8; 8; 8; 8; 8];
[9; 9; 9; 9; 9; 9; 9; 9; 9]; [10; 10; 10; 10; 10; 10; 10; 10; 10; 10];
[11; 11; 11; 11; 11; 11; 11; 11; 11; 11; 11];
[12; 12; 12; 12; 12; 12; 12; 12; 12; 12; 12; 12];
[13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13; 13];
[14; 14; 14; 14; 14; 14; 14; 14; 14; 14; 14; 14; 14; 14]]

Each inner list contains n copies of the number n. Note how the indentation and line breaks are somewhat sophisticated.
All the inner lists are indented one space from the left-hand margin. Line breaks have been inserted to avoid splitting
inner lists over multiple lines.
The Format module is what provides this functionality, and Format.formatter is an abstract type in it. You could
think of a formatter as being a place to send output, like a file, and have it be automatically formatted along the way. The
typical use of a formatter is as argument to a function such as Format.fprintf, which like Printf uses format
specifiers.
For example, suppose you wanted to change how strings are printed by the toplevel and add ” kupo” to the end of each
string. Here’s code that would do it:

let kupo_pp fmt s = Format.fprintf fmt "%s kupo" s;;


#install_printer kupo_pp;;

val kupo_pp : Format.formatter -> string -> unit = <fun>

Now you can see that the toplevel adds ” kupo” to each string while printing it, even though it’s not actual a part of the
original string:

let h = "Hello"
let s = String.length h

val h : string = Hello kupo

val s : int = 5

To keep ourselves from getting confused about strings in the rest of this section, let’s uninstall that pretty printer before
going on:

#remove_printer kupo_pp;;

As a bigger example, let’s add pretty printing to ListStack:

module type Stack = sig


type 'a t
exception Empty
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
val peek : 'a t -> 'a
val pop : 'a t -> 'a t
val size : 'a t -> int
val pp :
(continues on next page)

194 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


(Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
end

First, notice that we have to expose pp as part of the module type. Otherwise it would be encapsulated, hence we wouldn’t
be able to install it. Second, notice that the type of pp now takes an extra first argument of type Format.formatter
-> 'a -> unit. That is itself a pretty printer for type 'a, on which t is parameterized. We need that argument in
order to be able to pretty print the values of type 'a.

module ListStack : Stack = struct


type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
let pp pp_val fmt s =
let open Format in
let pp_break fmt () = fprintf fmt "@," in
fprintf fmt "@[<v 0>top of stack";
if s <> [] then fprintf fmt "@,";
pp_print_list ~pp_sep:pp_break pp_val fmt s;
fprintf fmt "@,bottom of stack@]"
end

In ListStack.pp, we use some of the advanced features of the Format module. Function Format.
pp_print_list does the heavy lifting to print all the elements of the stack. The rest of the code handles the in-
dentation and line breaks. Here’s the result:

#install_printer ListStack.pp

ListStack.empty

- : 'a ListStack.t = top of stack


bottom of stack

ListStack.(empty |> push 1 |> push 2)

- : int ListStack.t = top of stack


2
1
bottom of stack

For more information, see the toplevel manual (search for #install_printer), the Format module, and this OCaml
GitHub issue. The latter seems to be the only place that documents the use of extra arguments, as in pp_val above, to
print values of polymorphic types.

7.4. Encapsulation 195


OCaml Programming: Correct + Efficient + Beautiful

7.5 Compilation Units

A compilation unit is a pair of OCaml source files in the same directory. They share the same base name, call it x, but
their extensions differ: one file is x.ml, the other is x.mli. The file x.ml is called the implementation, and x.mli is
called the interface.
For example, suppose that foo.mli contains exactly the following:

val x : int
val f : int -> int

and foo.ml, in the same directory, contains exactly the following:

let x = 0
let y = 12
let f x = x + y

Then compiling foo.ml will have the same effect as defining the module Foo as follows:

module Foo : sig


val x : int
val f : int -> int
end = struct
let x = 0
let y = 12
let f x = x + y
end

In general, when the compiler encounters a compilation unit, it treats it as defining a module and a signature like this:

module Foo
: sig (* insert contents of foo.mli here *) end
= struct
(* insert contents of foo.ml here *)
end

The unit name Foo is derived from the base name foo by just capitalizing the first letter. Notice that there is no named
module type being defined; the signature of Foo is actually anonymous.
The standard library uses compilation units to implement most of the modules we have been using so far, like List and
String. You can see that in the standard library source code.

7.5.1 Documentation Comments

Some documentation comments belong in the interface file, whereas others belong in the implementation file:
• Clients of an abstraction can be expected to read interface files, or rather the HTML documentation generated
from them. So the comments in an interface file should be written with that audience in mind. These comments
should describe how to use the abstraction, the preconditions for calling its functions, what exceptions they might
raise, and perhaps some notes on what algorithms are used to implement operations. The standard library’s List
module contains many examples of these kinds of comments.
• Clients should not be expected to read implementation files. Those files will be read by creators and maintainers
of the implementation. The documentation in the implementation file should provide information that explains
the internal details of the abstraction, such as how the representation type is used, how the code works, important

196 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

internal invariants it maintains, and so forth. Maintainers can also be expected to read the specifications in the
interface files.
Documentation should not be duplicated between the files. In particular, the client-facing specification comments in the
interface file should not be duplicated in the implementation file. One reason is that duplication inevitably leads to errors.
Another reason is that OCamldoc has the ability to automatically inject the comments from the interface file into the
generated HTML from the implementation file.
OCamldoc comments can be placed either before or after an element of the interface. For example, both of these place-
ments are possible:

(** The mathematical constant 3.14... *)


val pi : float

val pi : float
(** The mathematical constant 3.14... *)

Tip: The standard library developers apparently prefer the post-placement of the comment, and OCamlFormat seems
to work better with that, too.

7.5.2 An Example with Stacks

Put this code in mystack.mli, noting that there is no sig..end around it or any module type:

type 'a t
exception Empty
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
val peek : 'a t -> 'a
val pop : 'a t -> 'a t

We’re using the name “mystack” because the standard library already has a Stack module. Re-using that name could
lead to error messages that are somewhat hard to understand.
Also put this code in mystack.ml, noting that there is no struct..end around it or any module:

type 'a t = 'a list


exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push = List.cons
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s

Create a dune file:

(library
(name mystack))

Compile the code and launch utop:

7.5. Compilation Units 197


OCaml Programming: Correct + Efficient + Beautiful

$ dune utop

Your compilation unit is ready for use:

# Mystack.empty;;
- : 'a Mystack.t = <abstr>

7.5.3 Incomplete Compilation Units

What if either the interface or implementation file is missing for a compilation unit?
Missing Interface Files. Actually this is exactly how we’ve normally been working up until this point. For example,
you might have done some homework in a file named lab1.ml but never needed to worry about lab1.mli. There
is no requirement that every .ml file have a corresponding .mli file, or in other words, that every compilation unit be
complete.
If the .mli file is missing there is still a module that is created, as we saw back when we learned about #load and
modules. It just doesn’t have an automatically imposed signature. For example, the situation with lab1 above would
lead to the following module being created during compilation:

module Lab1 = struct


(* insert contents of lab1.ml here *)
end

Missing Implementation Files. This case is much rarer, and not one you are likely to encounter in everyday development.
But be aware that there is a misuse case that Java or C++ programmers sometimes accidentally fall into. Suppose you
have an interface for which there will be a few implementations. Thinking back to stacks earlier in this chapter, perhaps
you have a module type Stack and two modules that implement it, ListStack and CustomStack:

module type Stack = sig


type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
(* etc. *)
end

module ListStack : Stack = struct


type 'a t = 'a list
let empty = []
let push = List.cons
(* etc. *)
end

module CustomStack : Stack = struct


(* omitted *)
end

It’s tempting to divide that code up into files as follows:

(********************************)
(* stack.mli *)
type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
(continues on next page)

198 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


(* etc. *)

(********************************)
(* listStack.ml *)
type 'a t = 'a list
let empty = []
let push = List.cons
(* etc. *)

(********************************)
(* customStack.ml *)
(* omitted *)

The reason it’s tempting is that in Java you might put the Stack interface into a Stack.java file, the ListStack
class in a ListStack.java file, and so forth. In C++ something similar might be done with .hpp and .cpp files.
But the OCaml file organization shown above just won’t work. To be a compilation unit, the interface for listStack.
ml must be in listStack.mli. It can’t be in a file with any other name. So there’s no way with that code division to
stipulate that ListStack : Stack.
Instead, the code could be divided like this:

(********************************)
(* stack.ml *)
module type S = sig
type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
(* etc. *)
end

(********************************)
(* listStack.ml *)
module M : Stack.S = struct
type 'a t = 'a list
let empty = []
let push = List.cons
(* etc. *)
end

(********************************)
(* customStack.ml *)
module M : Stack.S = struct
(* omitted *)
end

Note the following about that division:


• The module type goes in a .ml file not a .mli, because we’re not trying to create a compilation unit.
• We give short names to the modules and module types in the files, because they will already be inside a module
based on their filename. It would be rather verbose, for example, to name S something longer like Stack. If we
did, we’d have to write Stack.Stack in the module type annotations instead of Stack.S.
Another possibility for code division would be to put all the code in a single file stack.ml. That works if all the code
is part of the same library, but not if (e.g.) ListStack and CustomStack are developed by separate organizations.
If it is in a single file, then we could turn it into a compilation unit:

7.5. Compilation Units 199


OCaml Programming: Correct + Efficient + Beautiful

(********************************)
(* stack.mli *)
module type S = sig
type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
(* etc. *)
end

module ListStack : S

module CustomStack : S

(********************************)
(* stack.ml *)
module type S = sig
type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
(* etc. *)
end

module ListStack : S = struct


type 'a t = 'a list
let empty = []
let push = List.cons
(* etc. *)
end

module CustomStack : S = struct


(* omitted *)
end

Unfortunately that does mean we’ve duplicated Stack.S in both the interface and implementation files. There’s no way
to automatically “import” an already declared module type from a .mli file into the corresponding .ml file.

7.6 Functional Data Structures

A functional data structure is one that does not make use of mutability. It’s possible to build functional data structures
both in functional languages and in imperative languages. For example, you could build a Java equivalent to OCaml’s
list type by creating a Node class whose fields are immutable by virtue of using the const keyword.
Functional data structures have the property of being persistent: updating the data structure with one of its operations
does not change the existing version of the data structure but instead produces a new version. Both exist and both can
still be accessed. A good language implementation will ensure that any parts of the data structure that are not changed
by an operation will be shared between the old version and the new version. Any parts that do change will be copied so
that the old version may persist. The opposite of a persistent data structure is an ephemeral data structure: changes are
destructive, so that only one version exists at any time. Both persistent and ephemeral data structures can be built in both
functional and imperative languages.

200 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

7.6.1 Lists

The built-in singly-linked list data structure in OCaml is functional. We know that, because we’ve seen how to imple-
ment it with algebraic data types. It’s also persistent, which we can demonstrate:

let lst = [1; 2];;


let lst' = List.tl lst;;
lst;;

val lst : int list = [1; 2]

val lst' : int list = [2]

- : int list = [1; 2]

Taking the tail of lst does not change the list. Both lst and lst' coexist without affecting one another.

7.6.2 Stacks

We implemented stacks earlier in this chapter. Here’s a terse variant of one of those implementations, in which we add a
to_list operation to make it easier to view the contents of the stack in examples:

module type Stack = sig


type 'a t
exception Empty
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
val peek : 'a t -> 'a
val pop : 'a t -> 'a t
val size : 'a t -> int
val to_list : 'a t -> 'a list
end

module ListStack : Stack = struct


type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push = List.cons
let peek = function [] -> raise Empty | x :: _ -> x
let pop = function [] -> raise Empty | _ :: s -> s
let size = List.length
let to_list = Fun.id
end

That implementation is functional, as can be seen above, and also persistent:

open ListStack;;
let s = empty |> push 1 |> push 2;;
let s' = pop s;;
to_list s;;
to_list s';;

7.6. Functional Data Structures 201


OCaml Programming: Correct + Efficient + Beautiful

val s : int ListStack.t = <abstr>

val s' : int ListStack.t = <abstr>

- : int list = [2; 1]

- : int list = [1]

The value s is unchanged by the pop operation that creates s'. Both versions of the stack coexist.
The Stack module type gives us a strong hint that the data structure is persistent in the types it provides for push and
pop:

val push : 'a -> 'a t -> 'a t


val pop : 'a t -> 'a t

Both of those take a stack as an argument and return a new stack as a result. An ephemeral data structure usually would
not bother to return a stack. In Java, for example, similar methods might have a void return type; the equivalent in
OCaml would be returning unit.

7.6.3 Options vs Exceptions

All of our stack implementations so far have raised an exception whenever peek or pop is applied to the empty stack.
Another possibility would be to use an option for the return value. If the input stack is empty, then peek and pop
return None; otherwise, they return Some.

module type Stack = sig


type 'a t
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
val peek : 'a t -> 'a option
val pop : 'a t -> 'a t option
val size : 'a t -> int
val to_list : 'a t -> 'a list
end

module ListStack : Stack = struct


type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push = List.cons
let peek = function [] -> None | x :: _ -> Some x
let pop = function [] -> None | _ :: s -> Some s
let size = List.length
let to_list = Fun.id
end

But that makes it harder to pipeline:

ListStack.(empty |> push 1 |> pop |> peek)

202 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

File "[5]", line 1, characters 11-33:


1 | ListStack.(empty |> push 1 |> pop |> peek)
^^^^^^^^^^^^^^^^^^^^^^
Error: This expression has type int ListStack.t option
but an expression was expected of type 'a ListStack.t

The types break down for the pipeline right after the pop, because that now returns an 'a t option, but peek
expects an input that is merely an 'a t.
It is possible to define some additional operators to help restore the ability to pipeline. In fact, these functions are already
defined in the Option module in the standard library, though not as infix operators:

(* Option.map aka fmap *)


let ( >>| ) opt f =
match opt with
| None -> None
| Some x -> Some (f x)

(* Option.bind *)
let ( >>= ) opt f =
match opt with
| None -> None
| Some x -> f x

val ( >>| ) : 'a option -> ('a -> 'b) -> 'b option = <fun>

val ( >>= ) : 'a option -> ('a -> 'b option) -> 'b option = <fun>

We can use those as needed for pipelining:

ListStack.(empty |> push 1 |> pop >>| push 2 >>= pop >>| push 3 >>| to_list)

- : int list option = Some [3]

But it’s not so pleasant to figure out which of the three operators to use where.
There is therefore a tradeoff in the interface design:
• Using options ensures that surprising exceptions regarding empty stacks never occur at run-time. The program is
therefore more robust. But the convenient pipeline operator is lost.
• Using exceptions means that programmers don’t have to write as much code. If they are sure that an exception can’t
occur, they can omit the code for handling it. The program is less robust, but writing it is more convenient.
There is thus a tradeoff between writing more code early (with options) or doing more debugging later (with exceptions).
The OCaml standard library has recently begun providing both versions of the interface in a data structure, so that the
client can make the choice of how they want to use it. For example, we could provide both peek and peek_opt, and
the same for pop, for clients of our stack module:

module type Stack = sig


type 'a t
exception Empty
val empty : 'a t
val is_empty : 'a t -> bool
val push : 'a -> 'a t -> 'a t
(continues on next page)

7.6. Functional Data Structures 203


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


val peek : 'a t -> 'a
val peek_opt : 'a t -> 'a option
val pop : 'a t -> 'a t
val pop_opt : 'a t -> 'a t option
val size : 'a t -> int
val to_list : 'a t -> 'a list
end

module ListStack : Stack = struct


type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let push = List.cons
let peek = function [] -> raise Empty | x :: _ -> x
let peek_opt = function [] -> None | x :: _ -> Some x
let pop = function [] -> raise Empty | _ :: s -> s
let pop_opt = function [] -> None | _ :: s -> Some s
let size = List.length
let to_list = Fun.id
end

One nice thing about this implementation is that it is efficient. All the operations except for size are constant time. We
saw earlier in the chapter that size could be made constant time as well, at the cost of some extra space — though just
a constant factor more — by caching the size of the stack at each node in the list.

7.6.4 Queues

Queues and stacks are fairly similar interfaces. We’ll stick with exceptions instead of options for now.

module type Queue = sig


(** An ['a t] is a queue whose elements have type ['a]. *)
type 'a t

(** Raised if [front] or [dequeue] is applied to the empty queue. *)


exception Empty

(** [empty] is the empty queue. *)


val empty : 'a t

(** [is_empty q] is whether [q] is empty. *)


val is_empty : 'a t -> bool

(** [enqueue x q] is the queue [q] with [x] added to the end. *)
val enqueue : 'a -> 'a t -> 'a t

(** [front q] is the element at the front of the queue. Raises [Empty]
if [q] is empty. *)
val front : 'a t -> 'a

(** [dequeue q] is the queue containing all the elements of [q] except the
front of [q]. Raises [Empty] is [q] is empty. *)
val dequeue : 'a t -> 'a t

(continues on next page)

204 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


(** [size q] is the number of elements in [q]. *)
val size : 'a t -> int

(** [to_list q] is a list containing the elements of [q] in order from


front to back. *)
val to_list : 'a t -> 'a list
end

Important: Similarly to peek and pop, note how front and dequeue divide the responsibility of getting the first
element vs. getting all the rest of the elements.

It’s easy to implement queues with lists, just as it was for implementing stacks:

module ListQueue : Queue = struct


(** The list [x1; x2; ...; xn] represents the queue with [x1] at its front,
followed by [x2], ..., followed by [xn]. *)
type 'a t = 'a list
exception Empty
let empty = []
let is_empty = function [] -> true | _ -> false
let enqueue x q = q @ [x]
let front = function [] -> raise Empty | x :: _ -> x
let dequeue = function [] -> raise Empty | _ :: q -> q
let size = List.length
let to_list = Fun.id
end

module ListQueue : Queue

But despite being as easy, this implementation is not as efficient as our list-based stacks. Dequeueing is a constant-time
operation with this representation, but enqueueing is a linear-time operation. That’s because dequeue does a single
pattern match, whereas enqueue must traverse the entire list to append the new element at the end.
There’s a very clever way to do better on efficiency. We can use two lists to represent a single queue. This representation
was invented by Robert Melville as part of his PhD dissertation at Cornell (Asymptotic Complexity of Iterative Computa-
tions, Jan 1981), which was advised by Prof. David Gries. Chris Okasaki (Purely Functional Data Structures, Cambridge
University Press, 1988) calls these batched queues. Sometimes you will see this same implementation referred to as “im-
plementing a queue with two stacks”. That’s because stacks and lists are so similar (as we’ve already seen) that you could
rewrite pop as List.tl, and so forth.
The core idea has a Part A and a Part B. Part A is: we use the two lists to split the queue into two pieces, the inbox and
outbox. When new elements are enqueued, we put them in the inbox. Eventually (we’ll soon come to how) elements are
transferred from the inbox to the outbox. When a dequeue is requested, that element is removed from the outbox; or when
the front element is requested, we check the outbox for it. For example, if the inbox currently had [3; 4; 5] and the
outbox had [1; 2], then the front element would be 1, which is the head of the outbox. Dequeuing would remove that
element and leave the outbox with just [2]. Likewise, enqueuing 6 would make the inbox become [3; 4; 5; 6].
The efficiency of front and dequeue is very good so far. We just have to take the head or tail of the outbox, respec-
tively, assuming it is non-empty. Those are constant-time operations. But the efficiency of enqueue is still bad. It’s
linear time, because we have to append the new element to the end of the list. It’s too bad we have to use the append
operator, which is inherently linear time. It would be much better if we could use cons, which is constant time.
So here’s Part B of the core idea: let’s keep the inbox in reverse order. For example, if we enqueued 3 then 4 then 5,
the inbox would actually be [5; 4; 3], not [3; 4; 5]. Then if 6 were enqueued next, we could cons it onto the

7.6. Functional Data Structures 205


OCaml Programming: Correct + Efficient + Beautiful

beginning of the inbox, which becomes [6; 5; 4; 3]. The queue represented by inbox i and outbox o is therefore
o @ List.rev i. So enqueue can now always be a constant-time operation.
But what about dequeue (and front)? They’re constant time too, as long as the outbox is not empty. If it’s empty,
we have a problem. We need to transfer whatever is in the inbox to the outbox at that point. For example, if the outbox
is empty, and the inbox is [6; 5; 4; 3], then we need to switch them around, making the outbox be [3; 4; 5;
6] and the inbox be empty. That’s actually easy: we just have to reverse the list.
Unfortunately, we just re-introduced a linear-time operation. But with one crucial difference: we don’t have to do that
linear-time reverse on every dequeue, whereas with ListQueue above we had to do the linear-time append on every
enqueue. Instead, we only have to do the reverse on those rare occasions when the outbox becomes empty.
So even though in the worst case dequeue (and front) will be linear time, most of the time they will not be. In
fact, later in this book when we study amortized analysis we will show that in the long run they can be understood as
constant-time operations. For now, here’s a piece of intuition to support that claim: every individual element enters the
inbox once (with a cons), moves to the outbox once (with a pattern match then cons), and leaves the outbox once (with a
pattern match). Each of those is constant time. So each element only ever experiences constant-time operations from its
own perspective.
For now, let’s move on to implementing these ideas. In the implementation, we’ll add one more idea: the outbox always
has to have an element in it, unless the queue is empty. In other words, if the outbox is empty, we’re guaranteed the inbox
is too. That requirement isn’t necessary for batched queues, but it does keep the code simpler by reducing the number
of times we have to check whether a list is empty. The tiny tradeoff is that if the queue is empty, enqueue now has to
directly put an element into the outbox. No matter, that’s still a constant-time operation.

module BatchedQueue : Queue = struct


(** [{o; i}] represents the queue [o @ List.rev i]. For example,
[{o = [1; 2]; i = [5; 4; 3]}] represents the queue [1, 2, 3, 4, 5],
where [1] is the front element. To avoid ambiguity about emptiness,
whenever only one of the lists is empty, it must be [i]. For example,
[{o = [1]; i = []}] is a legal representation, but [{o = []; i = [1]}]
is not. This implies that if [o] is empty, [i] must also be empty. *)
type 'a t = {o : 'a list; i : 'a list}

exception Empty

let empty = {o = []; i = []}

let is_empty = function


| {o = []} -> true
| _ -> false

let enqueue x = function


| {o = []} -> {o = [x]; i = []}
| {o; i} -> {o; i = x :: i}

let front = function


| {o = []} -> raise Empty
| {o = h :: _} -> h

let dequeue = function


| {o = []} -> raise Empty
| {o = [_]; i} -> {o = List.rev i; i = []}
| {o = _ :: t; i} -> {o = t; i}

let size {o; i} = List.(length o + length i)

let to_list {o; i} = o @ List.rev i


(continues on next page)

206 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

(continued from previous page)


end

module BatchedQueue : Queue

The efficiency of batched queues comes at a price in readability. If we compare ListQueue and BatchedQueue, it’s
hopefully clear that ListQueue is a simple and correct implementation of a queue data structure. It’s probably far less
clear that BatchedQueue is a correct implementation. Just look at how many paragraphs of writing it took to explain
it above!

7.6.5 Maps

Recall that a map (aka dictionary) binds keys to values. Here is a module type for maps. There are many other operations
a map might support, but these will suffice for now.

module type Map = sig


(** [('k, 'v) t] is the type of maps that bind keys of type ['k] to
values of type ['v]. *)
type ('k, 'v) t

(** [empty] does not bind any keys. *)


val empty : ('k, 'v) t

(** [insert k v m] is the map that binds [k] to [v], and also contains
all the bindings of [m]. If [k] was already bound in [m], that old
binding is superseded by the binding to [v] in the returned map. *)
val insert : 'k -> 'v -> ('k, 'v) t -> ('k, 'v) t

(** [lookup k m] is the value bound to [k] in [m]. Raises: [Not_found] if [k]
is not bound in [m]. *)
val lookup : 'k -> ('k, 'v) t -> 'v

(** [bindings m] is an association list containing the same bindings as [m].


The keys in the list are guaranteed to be unique. *)
val bindings : ('k, 'v) t -> ('k * 'v) list
end

module type Map =


sig
type ('k, 'v) t
val empty : ('k, 'v) t
val insert : 'k -> 'v -> ('k, 'v) t -> ('k, 'v) t
val lookup : 'k -> ('k, 'v) t -> 'v
val bindings : ('k, 'v) t -> ('k * 'v) list
end

Note how Map.t is parameterized on two types, 'k and 'v, which are written in parentheses and separated by commas.
Although ('k, 'v) might look like a pair of values, it is not: it is a syntax for writing multiple type variables.
Recall that association lists are lists of pairs, where the first element of each pair is a key, and the second element is the
value it binds. For example, here is an association list that maps some well-known names to an approximation of their
numeric value:

7.6. Functional Data Structures 207


OCaml Programming: Correct + Efficient + Beautiful

[("pi", 3.14); ("e", 2.718); ("phi", 1.618)]

Naturally we can implement the Map module type with association lists:

module AssocListMap : Map = struct


(** The list [(k1, v1); ...; (kn, vn)] binds key [ki] to value [vi].
If a key appears more than once in the list, it is bound to the
the left-most occurrence in the list. *)
type ('k, 'v) t = ('k * 'v) list
let empty = []
let insert k v m = (k, v) :: m
let lookup k m = List.assoc k m
let keys m = List.(m |> map fst |> sort_uniq Stdlib.compare)
let bindings m = m |> keys |> List.map (fun k -> (k, lookup k m))
end

module AssocListMap : Map

This implementation of maps is persistent. For example, adding a new binding to the map m below does not change m
itself:

open AssocListMap
let m = empty |> insert "pi" 3.14 |> insert "e" 2.718
let m' = m |> insert "phi" 1.618
let b = bindings m
let b' = bindings m'

val m : (string, float) AssocListMap.t = <abstr>

val m' : (string, float) AssocListMap.t = <abstr>

val b : (string * float) list = [("e", 2.718); ("pi", 3.14)]

val b' : (string * float) list = [("e", 2.718); ("phi", 1.618); ("pi", 3.14)]

The insert operation is constant time, which is great. But the lookup operation is linear time. It’s possible to do much
better than that. In a later chapter, we’ll see how to do better. Logarithmic-time performance is achievable with balanced
binary trees, and something like constant-time performance with hash tables. Neither of those, however, achieves the
simplicity of the code above.
The bindings operation is complicated by potential duplicate keys in the list. It uses a keys helper function to extract
the unique list of keys with the help of library function List.sort_uniq. That function sorts an input list and in the
process discards duplicates. It requires a comparison function as input.

Note: A comparison function must return 0 if its arguments compare as equal, a positive integer if the first is greater,
and a negative integer if the first is smaller.

Here we use the standard library’s comparison function Stdlib.compare, which behaves essentially the same as the
built-in comparison operators =, <, >, etc. Custom comparison functions are useful if you want to have a relaxed notion
of what being a duplicate means. For example, maybe you’d like to ignore the case of strings, or the sign of a number,
etc.

208 Chapter 7. Modular Programming


OCaml Programming: Correct + Efficient + Beautiful

The running time of List.sort_uniq is linearithmic, and it produces a linear number of keys as output. For each of
those keys, we do a linear-time lookup operation. So the total running time of bindings is 𝑂(𝑛 log 𝑛) + 𝑂(𝑛) ⋅ 𝑂(𝑛),
which is 𝑂(𝑛2 ). We can definitely do better than that with more advanced data structures.
Actually we can have a constant-time bindings operation even with association lists, if we are willing to pay for a
linear-time insert operation:

module UniqAssocListMap : Map = struct


(** The list [(k1, v1); ...; (kn, vn)] binds key [ki] to value [vi].
No duplicate keys may occur. *)
type ('k, 'v) t = ('k * 'v) list
let empty = []
let insert k v m = (k, v) :: List.remove_assoc k m
let lookup k m = List.assoc k m
let bindings m = m
end

module UniqAssocListMap : Map

That implementation removes any duplicate binding of k before inserting a new binding.

7.6.6 Sets

Here is a module type for sets. There are many other operations a set data structure might be expected to support, but
these will suffice for now.

module type Set = sig


(** ['a t] is the type of sets whose elements are of type ['a]. *)
type 'a t

(** [empty] is the empty set. *)


val empty : 'a t

(** [mem x s] is whether [x] is an element of [s]. *)


val mem : 'a -> 'a t -> bool

(** [add x s] is the set that contains [x] and all the elements of [s]. *)
val add : 'a -> 'a t -> 'a t

(** [elements s] is a list containing the elements of [s]. No guarantee