Documente Academic
Documente Profesional
Documente Cultură
The type keyword declares a name for the new type. The variant type is declared with a
set of constructors that describe the possible ways to make a value of that type. In this
case, we have three constructors: Yes, No, and Maybe. Constructor names must start with
an uppercase letter, and all other names in OCaml must start with a lowercase letter.
The different constructors can also carry other values with them. For example, suppose
we want a type that can either be a 2D point or a 3D point. It can be declared as follows:
Some examples of values of type eitherPoint are: TwoD (2.1, 3.0) and ThreeD
(1.0, 0.0, -1.0).
Suppose we have a value of type eitherPoint, which is either a TwoD of something or
ThreeD of something. We need a way to extract the "something". This can be done with
pattern matching. Example:
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 1/16
10/14/2020 Lecture 4: Variant types and Polymorphism
Variant types introduce new syntax for terms e, patterns p, and values v:
Note that the vertical bars in the expression "match e with p1 -> e1 | ... | pn -> en"
are part the syntax of this construct; the other vertical bars (|) are part of the BNF notation.
We can use variant types to define many useful data structures. In fact, the bool is really
just a variant type with constructors named true and false.
This type has two constructors, Nil and Cons. It is a recursive type because it mentions
itself in its own definition (in the Cons constructor), just like a recursive function is one that
mentions itself in its own definition.
Any list of integers can be represented by using this type. For example, the empty list is
just the constructor Nil, and Cons corresponds to the operator ::. Here are some
examples of lists:
So we can construct any lists we want. We can also take them apart using pattern
matching. For example, our length function above can be written for intlists by just
translating the list patterns into the corresponding patterns using constructors. Similarly, we
can implement many other functions over lists, as shown in the following examples.
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 2/16
10/14/2020 Lecture 4: Variant types and Polymorphism
match lst with
| Nil -> 0
| Cons (h, t) -> length t + 1
(* Notice that the match expressions for lists all have the same
* form -- a case for the empty list (Nil) and a case for a Cons.
* Also notice that for most functions, the Cons case involves a
* recursive function call. *)
(******************************
* Examples
******************************)
(* Given [i1; i2; ...; in], return [i1+1; i2+1; ...; in+n] *)
let rec addone_to_all (lst : intlist) : intlist =
match lst with
| Nil -> Nil
| Cons (h, t) -> Cons (inc h, addone_to_all t)
(* Given [i1; i2; ...; in], return [i1*i1; i2*i2; ...; in*in] *)
let rec square_all (lst : intlist) : intlist =
match lst with
| Nil -> Nil
| Cons (h, t) -> Cons (square h, square_all t)
(* Given a function f and [i1; ...; in], return [f i1; ...; f in].
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 4/16
10/14/2020 Lecture 4: Variant types and Polymorphism
* Notice how we factored out the common parts of addone_to_all
* and square_all. *)
let rec do_function_to_all (f : int -> int) (lst : intlist) : intlist =
match lst with
| Nil -> Nil
| Cons (h, t) -> Cons (f h, do_function_to_all f t)
(* Explicit versions *)
let rec sum (lst : intlist) : int =
match lst with
| Nil -> 0
| Cons (i, t) -> i + sum t
(* Given f, b, and [i1; i2; ...; in], return f(i1, f(i2, ..., f (in, b))).
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 5/16
10/14/2020 Lecture 4: Variant types and Polymorphism
* Again, we factored out the common parts of sum and product. *)
let rec collapse (f : int -> int -> int) (b : int) (lst : intlist) : int =
match lst with
| Nil -> b
| Cons (h, t) -> f h (collapse f b t)
(* Trees of integers *)
let tree1 =
Node {value=2; left=Node {value=1; left=Empty; right=Empty};
right=Node {value=3; left=Empty; right=Empty}}
Trees are another very useful data structure. Unlike lists, they are not built into OCaml. A
binary tree is either
Just for variety, let's 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:
The rule for when mutually recursive type declarations are legal is a little tricky. Essentially,
any cycle of recursive types must include at least one record or variant type. Since the cycle
between inttree and node includes both kinds of types, this declaration is legal.
2
/ \ Node {value=2; left=Node {value=1; left=Empty; right=Empty};
1 3 right=Node {value=3; left=Empty; right=Empty}}
Because there are several things stored in a tree node, it's helpful to use a record rather
than a tuple to keep them all straight. But a tuple would also have worked.
We can use pattern matching to write the usual algorithms for recursively traversing trees.
For example, here is a recursive search over the tree:
Of course, if we knew the tree obeyed the binary search tree invariant, we could have
written a more efficient algorithm.
We can even define data structures that act like numbers, demonstrating that we don't
really have to have numbers built into OCaml either! A natural number is either the value
zero or the successor of some other natural number. This definition leads naturally to the
following definition for values that act like natural numbers nat:
This is how you might define the natural numbers in a mathematical logic course. We have
defined a new type nat, and Zero and Next are constructors for values of this type. The
type nat is a recursive type, which allows us to build expressions that have an arbitrary
number of nested Next constructors. Such values act like natural numbers:
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 7/16
10/14/2020 Lecture 4: Variant types and Polymorphism
four;;
- : nat = Next (Next (Next (Next Zero)))
Here we're pattern-matching a value with type nat. If the value is Zero we evaluate to
true; otherwise we evaluate to false.
Here we determine the predecessor of a number. If the value of n matches Zero then we
raise an exception, since zero has no predecessor in the natural numbers. If the value
matches Next m for some value m (which of course also must be of type nat), then we
return m.
Similarly we can define a function to add two numbers:
If you were to try evaluating add four four, the interpreter would respond with:
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 8/16
10/14/2020 Lecture 4: Variant types and Polymorphism
That was pretty easy. Now we can write toInt (add four four) and get 8. How
about the inverse operation?
To determine whether a natural number is even or odd, we can write a pair of mutually
recursive functions:
You have to use the keyword and to combine mutually recursive functions like this.
Otherwise the compiler would give an error when you refer to odd before it has been
defined.
Finally we can define multiplication in terms of addition.
which gives
Pattern matching
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 9/16
10/14/2020 Lecture 4: Variant types and Polymorphism
It turns out that the syntax of OCaml patterns is richer than what we saw in the last lecture.
In addition to new kinds of terms for creating and projecting tuple and record values, and
creating and examining variant type values, we also have the ability to match patterns
against values to pull them apart into their parts.
When used properly, pattern matching leads to concise, clear code. This is because
OCaml pattern matching allows one pattern to appear as a subexpression of another pattern.
For example, we see above that Next n is a pattern, but so is Next (Next n). This
second pattern matches only on a value that has the form Next (Next v) for some value v
(that is, the successor of the successor of something), and binds the variable n to that
something, v.
Similarly, in our implementation of the nth function, earlier, a neat trick is to use pattern
matching to do the if n = 0 and the match at the same time. We pattern-match on the
tuple (lst, n):
Here, we've also added a clause to catch the empty list and raise an exception. We're also
using the wildcard pattern _ to match on the n component of the tuple, because we don't
need to bind the value of n to another variable—we already have n. We can make this code
even shorter; can you see how?
All natural numbers are nonnegative, but we can simulate integers in terms of the naturals
by using a representation consisting of a sign and magnitude:
Here we've defined integer to refer to a record type with two fields: sign and mag.
Remember that records are unordered, so there is no concept of a "first" field.
The declarations of sign and integer both create new types. However, it is possible to
write type declarations that simply introduce a new name for an existing type. For example, if
we wrote type number = int, then the types number and int could be used
interchangeably.
We can use the definition of integer to write some integers:
Here we're pattern-matching on a record type. Notice that in the third pattern we are doing
pattern matching because the mag field is matched against a pattern itself, Next n.
Remember that the patterns are tested in order. How does the meaning of this function
change if the first two patterns are swapped?
The predecessor function is very similar, and it should be obvious that we could write
functions to add, subtract, and multiply integers in this representation.
OCaml syntax
Taking into account the ability to write complex patterns, we can now write down a more
comprehensive syntax for OCaml.
syntactic syntactic variables and grammar
examples
class rule(s)
identifiers x, y a, x, y, x_y, foo1000, ...
datatypes,
datatype X, Y Nil, Cons, list
constructors
...~2, ~1, 0, 1, 2 (integers)
1.0, ~0.001, 3.141 (floats)
constants c true, false (booleans)
"hello", "", "!" (strings)
#"A", #" " (characters)
unary
operator
u ~, not, size, ...
binary
operators
b +, *, -, >, <, >=, <=, ^, ...
e ::- c | x | u e | e1 b e2
| if e1 then e2 else e3 |
expressions let d1...dn in e end | e (e1, ..., en) | ~0.001, foo, not b,
(terms) (e1,...,en) | #n e | 2 + 2, Cons(2, Nil)
{x1=e1, ..., xn=en} | #x e | X(e) |
match e with p1->e1 | ... | pn->en
Note: pattern-matching floating point constants is not possible. So in the production "p ::=
c | .." above, c is an integer, boolean, string, or character constant, but not float.
Polymorphism
There is a nice feature that allows us to avoid rewriting the same code over and over so
that it works for different types. Suppose we want to write a function that swaps the position
of values in an ordered pair:
This is tedious, because we're writing exactly the same algorithm each time. It gets worse!
What if the two pair elements have different types?
Instead of writing explicit types for x and y, we write type variables 'a and 'b. The type of
swap is 'a * 'b -> 'b * 'a. This means that we can use swap as if it had any type that
we could get by consistently replacing 'a and 'b in its type with a type for 'a and a type for
'b. We can use the new swap in place of all the old definitions:
In fact, we can leave out the type declarations in the definition of swap, and OCaml will
figure out the most general polymorphic type it can be given, automatically:
The ability to use swap as though it had many different types is known as polymorphism,
from the Greek for "many forms".
Notice that the type variables must be substituted consistently in any use of a polymorphic
expression. For example, it is impossible for swap to have the type (int * float) ->
(string * int), because that type would consistently substitute for the type variable 'a
but not for 'b.
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 12/16
10/14/2020 Lecture 4: Variant types and Polymorphism
OCaml programmers typically read the types 'a and 'b as "alpha" and "beta". This is
easier than saying "single quotation mark a" or "apostrophe a". They also they wish they
could write Greek letters instead. A type variable may be any identifier preceded by a single
quotation mark; for example, 'key and 'value are also legal type variables. The OCaml
compiler needs to have these identifiers preceded by a single quotation mark so that it
knows it is seeing a type variable.
It is important to note that to be polymorphic in a parameter x, a function may not use x in
any way that would identify its type. It must treat x as a black box. Note that swap doesn't
use its arguments x or y in any interesting way, but treats them as black boxes. When the
OCaml type checker is checking the definition of swap, all it knows is that x is of some
arbitrary type 'a. It doesn't allow any operation to be performed on x that couldn't be
performed on an arbitrary type. This means that the code is guaranteed to work for any x
and y. However, we can apply other polymorphic functions. For example,
# let appendToString ((x : 'a), (s : string), (convert : 'a -> string)) : string =
(convert x) ^ " " ^ s;;
val appendToString : 'a * string * ('a -> string) -> string = <fun>
# appendToString (3110, "class", string_of_int);;
- : string = "3110 class"
# appendToString ("ten", "twelve", fun (s : string) -> s ^ " past");;
- : string = "ten past twelve"
Parameterized Types
We can also define polymorphic datatypes. For example, we defined lists of integers as
But we can make this more general by using a parameterized variant type instead:
let il : int list_ = Cons (1, Cons (2, Cons (3, Nil))) (* [1; 2; 3] *)
let fl : float list_ = Cons (3.14, Cons (2.17, Nil)) (* [3.14; 2.17] *)
let sl : string list_ = Cons ("foo", Cons ("bar", Nil)) (* ["foo"; "bar"] *)
let sil : (string * int) list_ =
Cons (("foo", 1), Cons (("bar", 2), Nil)) (* [("foo", 1); ("bar", 2)] *)
Notice list_ itself is not a type. We can think of list_ as a function that, when applied
to a type like int, produces another type (int list_). It is a parameterized type
constructor: a function that takes in parameters and gives back a type. Other languages
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 13/16
10/14/2020 Lecture 4: Variant types and Polymorphism
have parameterized type constructors. For example, in Java you can declare a
parameterized class:
class List<T> {
T head;
List <T> tail;
...
}
In OCaml, we can define polymorphic functions that know how to manipulate any kind of
list:
(* polymorphic lists *)
(* [1; 2; 3] *)
let il = Cons (1, Cons (2, Cons (3, Nil)))
let il2 = append il il
let il4 = append il2 il2
let il8 = append il4 il4
(* ["a"; "b"; "c"] *)
let sl = Cons ("a", Cons ("b", Cons ("c", Nil)))
let sl2 = append sl sl
let sl4 = append sl2 sl2
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 14/16
10/14/2020 Lecture 4: Variant types and Polymorphism
For trees,
type 'a tree = Leaf | Node of ('a tree) * 'a * ('a tree)
If we use a record type for the nodes, the record type also must be parameterized, and
instantiated on the same element type as the tree type:
It is also possible to have multiple type parameters on a parameterized type, in which case
parentheses are needed:
This grammar has exactly the same structure as the following type declarations:
type id = string
type baseType = Int | Real | String | Bool | Char
type mlType = Base of baseType | Arrow of mlType * mlType
| Product of mlType list | Record of (id * mlType) list
| DatatypeName of id
Any legal OCaml type expression can be represented by a value of type mlType that
contains all the information of the corresponding type expression. This value is known as the
abstract syntax for that expression. It is abstract because it doesn't contain any information
about the actual symbols used to represent the expression in the program. For example, the
abstract syntax for the expression int * bool -> {name : string} would be:
The abstract syntax would be exactly the same even for a more verbose version of the
same type expression: ((int * bool) -> {name : string}). Compilers typically use
abstract syntax internally to represent the program that they are compiling. We will see a lot
more abstract syntax later in the course when we see how OCaml works.
https://www.cs.cornell.edu/courses/cs3110/2014sp/lectures/4/variant-types-and-polymorphism.html 16/16