This document does very little to explain use-cases for the selected features but rather focuses on syntax. For a more in-depth coverage of all of these features I recommend reading the OCaml Document and User’s Manual and Real World OCaml.
For each feature there is a small explanation of the syntax, some examples, and links for further reading. As such this document is ideal for someone who wants to get a taste of the features of OCaml or who want to learn more about a specific feature.
If you have any comments please reach out to me at mads379@gmail.com or @mads_hartmann on twitter.
If you want to play around with the code I recommend using this online REPL.
Note: This is still a work in progress. I haven’t yet covered the following: pattern matching, modules, functors, Abstract Data Types, and much more.
You can find a Japanese translation of this post here
Table of Contents
- Table of Contents
- Lists, Arrays, and Tuples
- Let-bindings
- Functions
- Records
- Variants
- Exceptions
- Generalized Algebraic Datatypes
Lists, Arrays, and Tuples
A List
is constructed using square brackets where the elements are separated
by semi-colons:
[ 1 ; 2; 3 ];;
An Array
is constructed using square brackets with bars where the elements
are separated by semi-colons:
[| 1 ; 2; 3 |];;
Tuples
are constructed using parens and the elements are separated using
commas:
( "foo", "bar", "foobar" )
- : string * string * string = ("foo", "bar", "foobar")
Let-bindings
A let-binding associates an identifier with a value. It is introduced using the
following syntax let <identifier> = <expression> in <expression>
.
let hi = "hi " in
let there = "there!" in
hi ^ there;;
hi there!
The ^
is a function that concatenates two strings. The ^
function is used
as an infix operator in the example above but it could just as easily have been
invoked in a prefix manner as shown below.
(^) "Hi " "there";;
Hi there
Let-bindings are also used to declare functions. More on that in the next section.
Functions
As was shown in the previous section you invoke a function by adding the arguments after the function name separated by whitespace.
min 42 10;;
10
It might take some time to get used to separating function arguments with whitespace rather than commas as you do in other programming languages. In another language the above example might look like this:
min(10,24);;
- : int * int -> int * int = <fun>
However, in OCaml this will partially
apply the function min
with one argument, the tuple (10, 24)
. Partial application is another nice
feature of OCaml that allows you to supply N
arguments to a function of arity
X
and get a function of arity X-N
in return. This is possible as all
functions in OCaml are curried.
let at_least_42 = max 42 in
at_least_42 22;;
42
Defining functions
A function is defined using the following syntax
let <name> <arg1> <arg2> = <expression> in <expression>
, that is, the
function name followed by it’s arguments separated by whitespace.
The following is a function that takes one argument x
(which is inferred to
be an integer) and returns the square value of that integer.
let square x = x * x;;
val square : int -> int = <fun>
You can use the fun
keyword to introduce a lambda, it has the following
syntax fun <arg1> <arg2> -> <expression>
. So the example above is equivalent
to this:
let square = fun x -> x * x;;
val square : int -> int = <fun>
As mentioned earlier some functions can be used as infix operators. A
function can be used as in infix operator if one of the following names
are used ! $ % & * + - . / : < = > ? @ ^ | ~
. Read more about infix
and prefix functions in this
section
of Real World OCaml.
If your function only takes one argument and you want to pattern match
on it you can use the function
keyword (please ignore this horribly
inefficient implementation I’m about to show you):
let rec sum = function
| x :: xs -> x + (sum xs)
| [] -> 0
in sum [1;2;3;4;5;1;2];;
18
More on pattern matching
later.
The previous example also shows that if you want to define a recursive
function in
OCaml you have to use the rec
keyword.
Labeled arguments
By prefixing an argument with ~
you can give it a label which makes
the code easier to read and makes the order of the arguments irrelevant.
let welcome ~greeting ~name = Printf.printf "%s %s\n" greeting name in
welcome ~name:"reader" ~greeting:"Hi"
Hi reader
- : unit = ()
Shayne Fletcher has written an excellent blog post that goes into both labeled and optional arguments (described below) in more detail.
Optional arguments
By prefixing an argument with ?
you can make it optional. The value of
optional arguments are represented using the Option
type.
let welcome ?greeting_opt name =
let greeting = match greeting_opt with
| Some greeting -> greeting
| None -> "Hi"
in
Printf.printf "%s %s\n" greeting name
in
welcome ~greeting_opt:"Hey" "reader" ;
welcome ?greeting_opt:None "reader"
Hey reader
Hi reader
- : unit = ()
Default arguments
For optional arguments you can provide a default value. Thus the previous example could also have been written as such:
let welcome ?(greeting="Hi") name =
Printf.printf "%s %s\n" greeting name
in
welcome ~greeting:"Hey" "reader" ;
welcome "reader"
Hey reader
Hi reader
- : unit = ()
Records
Records are used to store a collection of values together as a single
value. The example below defines a record named person
with two
components.
type person = {
name: string;
age: int;
} ;;
let p = { name = "Mads" ; age = 25 } in
Printf.printf "%s is %d years old" p.name p.age
Mads is 25 years old- : unit = ()
Records can be parameterized using a polymorphic type.
type 'a ranked = {
item: 'a;
rank: int;
};;
let item = { item = Some 42 ; rank = 1 }
val item : int option ranked = {item = Some 42; rank = 1}
There is a lot more to be said of records. See this this section of Real World OCaml and this section of the OCaml Users Guide.
Variants
Variants, also known as algebraic data types, are commonly used to define recursive data structures. Just like records they can be parameterized using a polymorphic type as shown in the example below where a variant is used to represent a binary tree.
type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a * 'a tree;;
Node ((Leaf "foo"), "bar", (Leaf "foobar"));;
- : string tree = Node (Leaf "foo", "bar", Leaf "foobar")
The type tree
has two constructors: Leaf
and Node
. The example
below shows one way to compute the height of such a tree:
let rec depth = function
| Leaf _ -> 1
| Node (left, _ ,right) -> 1 + max (depth left) (depth right)
in
let tree =
Node ((Leaf 1), 2, (Node ((Leaf 3), 4, (Node ((Leaf 5), 6, (Leaf 6))))))
in
depth tree;;
4
The example above uses the function
keyword to define a function that
takes a single argument that is pattern matched on.
Variants are one of the most useful features of OCaml so it’s well worth spending some more time studying them. See this section of Real World OCaml for information on use-cases and best-practices.
Polymorphic Variants
OCaml also has another type of variants that they call polymorphic variants. When using polymorphic variants you do not need to define the constructors prior to using them - you can think of them as an ad-hoc version of the regular variants.
let length_of_title book_title =
match String.length book_title with
| length when length <= 5 -> `Short
| length when length <= 10 -> `Medium
| _ -> `Long
in
length_of_title "The Hitchhiker's Guide to the Galaxy"
- : [> `Long | `Medium | `Short ] = `Long
Once again this feature is thoroughly covered in this section of Real World OCaml.
Extensible Variants
As the name suggests extensible variants are variants that can be extended with new constructors.
This is a new feature that was introduced in OCaml 4.02 and as such I haven’t yet used these in my own code so I will stick to the examples shown in the OCaml Users Manual.
type attr = .. ;;
type attr += Str of string ;;
type attr +=
| Int of int
| Float of float ;;
type attr += Int of int | Float of float
For more information read this section of the OCaml Users Manual. This features was released after Real World Ocaml and as such it isn’t covered in the book unfortunately. I look forward to then next revision.
Exceptions
OCaml gives you the posibility of using exceptions for signaling and handling exceptional conditions in your programs.
Exceptions are not checked, meaning that the OCaml compiler does not enforce that you catch exceptions.
You can define your own exceptions in a way similar to variants.
exception BadRequest of int * string
You signal an exception using the raise
keyword.
raise (BadRequest (500, "Oh noes, the service broke"))
You catch exceptions using the following syntax
try int_of_string "foo" with
| Failure msg -> -1
-1
Since OCaml 4.02 you can catch exceptions in pattern matches.
match int_of_string "foo" with
| n -> Printf.printf "Got an integer %d" n
| exception Failure msg -> Printf.printf "Caught exception with message %s" msg
Caught exception with message int_of_string- : unit = ()
As always, for more information read the section in the OCaml Users Manual.
Generalized Algebraic Datatypes
Generalized Algebraic Datatypes (GADTs) are an extension of the normal variants (algebraic datatypes). They can be used to allow the OCaml compiler to perform more sophisticated type checking or to control the memory representation of your data. I wrote a blog post about the former and Janestreet has written a great blog post on the latter.
The following is an example from my blog post. I include it here as an example of the syntax you use to define GADTs.
type _ value' =
| GBool : bool -> bool value'
| GInt : int -> int value'
type _ expr' =
| GValue : 'a value' -> 'a expr'
| GIf : bool expr' * 'a expr' * 'a expr' -> 'a expr'
| GEq : 'a expr' * 'a expr' -> bool expr'
| GLt : int expr' * int expr' -> bool expr'
GADTs were introduced in OCaml 4.00. The following section in the OCaml Users Manual describe the syntax and contains a few examples.