Lists
In OCaml, a list is a sequence of values of same type and is implemented as singly-linked lists. Here are some important operations for operating lists.
1 | (* Empty list *) [] |
Lists in OCaml are immutable, thus “mutating” a list in OCaml is equivalent to creating a new list. The compiler will intervene to make the memory usage efficient.
Moreover, when we have to pattern-match on the last argument, we can use the function syntactic sugar:
1 | let rec sum lst = function (* match lst with *) |
Variants
A variant is similar to enum in other languages, representing a value that is one of the several options.
1 | type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat |
If two variants are defined with overlapping constructor names, then it always be the type of later-defined one.
1 | type t1 = C | D |
Algebraic Data Types
variants can also carry data. Grammar is <constructor> of <type/record>, e.g.
1 | type point = float * float |
We can pattern match on ADTs
1 | let area = function |
Recursive Variants/Records
Variant types may mention themselves inside their own body. And can be mutually recursive with and keyword.
1 | type intlist = Nil | Cons of int * intlist |
But there’s constraint on mutually recursive variants: any such mutual recursion must involve at least one variant or record type that the recursion “goes through”.
1 | (* This is prohibited *) |
Records
A record is a composite of other types of data, each of which is named. OCaml records are much like structs in C.
1 | type ptype = TNormal | TFire | TWater |
To build a value of a record type, we use {} syntax. To access fields, we use . notation.
1 | let c = {name = "Charmander"; hp = 39; ptype = TFire};; |
We can also pattern match on records, if names are not provided, OCaml will by default use field names as bindings
1 | match c with {name = n; hp = h; ptype = t} -> h |
Sometimes, we want to create a new copy of record from some old records, we can use with syntax. After the with syntax, we do not need to bind new values to all fields, only part of fields are required.
1 | {e with f1 = e1; ...; fn = en} |
Tuples
Just like tuples in other languages. We can pattern match on tuples
1 | match (1, 2, 3) with (x, y, z) -> x + y + z |
Note that, if a tuple is of type (A, B, C), then its type annotation is A * B * C.
Options
Similar to Maybe in Haskell and Option<T> in Rust.
1 | let extract o = |
Like
list, we calloptiona type constructor: given a type, it produces a new type; but, it is not itself a type. So for any typet, we can writet optionas a type. Butoptionall by itself cannot be used as a type. Values of typet optionmight contain a value of typet, or they might contain nothing.Nonehas type'a optionbecause it’s unconstrained what the type is of the thing inside — as there isn’t anything inside.
Type Synonyms
Type Synonyms
A type synonym is a new name for an already existing type.
1 | type point = float * float |
Parameterized Types
Parameterized Types
Similar to C++ template, e.g. the following example defines a list with any type. The mylist is a type constructor, it accepts a type and returns a type, but it itself is not a type.
1 | type 'a mylist = Nil | Cons of 'a * 'a mylist |
For multiple parameterized types, we should use ():
1 | type ('a, 'b) pair = {first : 'a; second : 'b} |
Anonymous Variants
If sometimes, we just want to define a variant for the return value, e.g.
1 | type fin_or_inf = Finite of int | Infinity |
Instead of always explicit defining such a variant, we can use polymorphic variants. They don’t have a name, no need for declare-before-use, and starts with a backquote. So we can rewrite the above as:
1 | let f = function |
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:
1 | match f 3 with |