Dowek Principles of Programming Languages c2009
Dowek Principles of Programming Languages c2009
Dowek Principles of Programming Languages c2009
Undergraduate Topics in Computer Science (UTiCS) delivers high-quality instructional content for
undergraduates studying in all areas of computing and information science. From core foundational
and theoretical material to final-year topics and applications, UTiCS books take a fresh, concise, and
modern approach and are ideal for self-study or for a one- or two-semester course. The texts are all
authored by established experts in their fields, reviewed by an international advisory board, and contain
numerous examples and problems. Many include fully worked solutions.
Iain D. Craig
Object-Oriented Programming Languages: Interpretation
978-1-84628-773-2
Max Bramer
Principles of Data Mining
978-1-84628-765-7
Hanne Riis Nielson and Flemming Nielson
Semantics with Applications: An Appetizer
978-1-84628-691-9
Michael Kifer and Scott A. Smolka
Introduction to Operating System Design and Implementation: The OSP 2 Approcah
978-1-84628-842-5
Phil Brooke and Richard Paige
Practical Distributed Processing
978-1-84628-840-1
Frank Klawonn
Computer Graphics with Java
978-1-84628-847-0
David Salomon
A Concise Introduction to Data Compression
978-1-84800-071-1
David Makinson
Sets, Logic and Maths for Computing
978-1-84628-844-9
Orit Hazzan
Agile Software Engineering
978-1-84800-198-5
Pankaj Jalote
A Concise Introduction to Software Engineering
978-1-84800-301-9
Alan P. Parkes
A Concise Introduction to Languages and Machines
978-1-84800-120-6
Gilles Dowek
Principles
of Programming
Languages
123
Gilles Dowek
École Polytechnique
France
Series editor
Ian Mackie, École Polytechnique, France
Advisory board
Samson Abramsky, University of Oxford, UK
Chris Hankin, Imperial College London, UK
Dexter Kozen, Cornell University, USA
Andrew Pitts, University of Cambridge, UK
Hanne Riis Nielson, Technical University of Denmark, Denmark
Steven Skiena, Stony Brook University, USA
Iain Stewart, University of Durham, UK
David Zhang, The Hong Kong Polytechnic University, Hong Kong
Based on course notes by Gilles Dowek published in 2006 by L’Ecole Polytechnique with the following
title: “Les principes des langages de programmation.”
We’ve known about algorithms for millennia, but we’ve only been writing com-
puter programs for a few decades. A big difference between the Euclidean or
Eratosthenes age and ours is that since the middle of the twentieth century,
we express the algorithms we conceive using formal languages: programming
languages.
Computer scientists are not the only ones who use formal languages. Op-
tometrists, for example, prescribe eyeglasses using very technical expressions,
such as “OD: -1.25 (-0.50) 180◦ OS: -1.00 (-0.25) 180◦ ”, in which the parenthe-
ses are essential. Many such formal languages have been created throughout
history: musical notation, algebraic notation, etc. In particular, such languages
have long been used to control machines, such as looms and cathedral chimes.
However, until the appearance of programming languages, those languages
were only of limited importance: they were restricted to specialised fields with
only a few specialists and written texts of those languages remained relatively
scarce. This situation has changed with the appearance of programming lan-
guages, which have a wider range of applications than the prescription of eye-
glasses or the control of a loom, are used by large communities, and have allowed
the creation of programs of many hundreds of thousands of lines.
The appearance of programming languages has allowed the creation of ar-
tificial objects, programs, of a complexity incomparable to anything that has
come before, such as steam engines or radios. These programs have, in return,
allowed the creation of other complex objects, such as integrated circuits made
of millions of transistors, or mathematical proofs that are hundreds of thou-
sands of pages long. It is very surprising that we have succeeded in writing
such complex programs in languages comprising such a small number of con-
structs — assignment, loops, etc. — that is to say in languages barely more
sophisticated than the language of prescription eyeglasses.
vii
viii Preface
1. Imperative Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Five Constructs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Assignment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.2 Variable Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.3 Sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.4 Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1.5 Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.1 Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.2 Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3 The Semantics of the Imperative Core . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.1 The Concept of a State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.2 Decomposition of the State . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3.3 A Visual Representation of a State . . . . . . . . . . . . . . . . . . . 10
1.3.4 The Value of Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.5 Execution of Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2. Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1 The Concept of Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.1 Avoiding Repetition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.2 Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.1.3 Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.1.4 The return Construct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.1.5 Functions and Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.1.6 Global Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.1.7 The Main Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
ix
x Contents
3. Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.1 Calling a Function from Inside the Body of that Function . . . . . 47
3.2 Recursive Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.2.1 Recursive Definitions and Circular Definitions . . . . . . . . . . 48
3.2.2 Recursive Definitions and Definitions by Induction . . . . . . 49
3.2.3 Recursive Definitions and Infinite Programs . . . . . . . . . . . . 49
3.2.4 Recursive Definitions and Fixed Point Equations . . . . . . . 51
3.3 Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4 C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.5 Programming Without Assignment . . . . . . . . . . . . . . . . . . . . . . . . . 55
4. Records . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.1 Tuples with Named Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.1.1 The Definition of a Record Type . . . . . . . . . . . . . . . . . . . . . 60
4.1.2 Allocation of a Record . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.1.3 Accessing Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.1.4 Assignment of Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.1.5 Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.1.6 The Semantics of Records . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2 Sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2.1 Sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.2.2 Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.2.3 Wrapper Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.3 Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.3.1 Definition of a Record Type . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.3.2 Creating a Record . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.3.3 Accessing Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Contents xi
7. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7.1 Exceptional Circumstances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7.2 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
7.3 Catching Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
7.4 The Propagation of Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
7.5 Error Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.6 The Semantics of Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.7 Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
8. Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
8.1 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
8.1.1 Functions as Part of a Type . . . . . . . . . . . . . . . . . . . . . . . . . 127
8.1.2 The Semantics of Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
8.2 Dynamic Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
8.3 Methods and Functional Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
8.4 Static Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
8.5 Static Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
8.6 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
8.7 Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
1
Imperative Core
1.1.1 Assignment
x = y;
y = 3;
x = x + 1;
x + 2 = y + 5;
are not.
To understand what happens when you execute the statement x = t; sup-
pose that within the recesses of your computer’s memory, there is a com-
partment labelled x. Executing the statement x = t; consists of filling this
compartment with the value of the expression t. The value previously contained
in compartment x is erased. If the expression t is a constant, for example 3,
its value is the same constant. If it is an expression with no variables, such as
3 + 4, its value is obtained by carrying out mathematical operations, in this
case, addition. If expression t contains variables, the values of these variables
must be looked up in the computer’s memory. The whole of the contents of the
computer’s memory is called a state.
Let us consider, initially, that expressions, such as x + 3, and statements,
such as y = x + 3;, form two disjoint categories. Later, however, we shall be
brought to revise this premise.
In these examples, the values of expressions are integers. Computers can
only store integers within a finite interval. In Java, integers must be between
-231 and 231 - 1, so there are 232 possible values. When a mathematical op-
eration produces a value outside of this interval, the result is kept within the
interval by taking its modulo 232 remainder. Thus, by adding 1 to 231 - 1, that
is to say 2147483647, we leave the interval and then return to it by removing
232 , which gives -231 or -2147483648.
Exercise 1.1
What is the value of the variable x after executing the following state-
ment?
x = 2 * 1500000000;
The integers are of type byte, short, int or long corresponding to the
intervals [-27 , 27 - 1], [-215 , 215 - 1], [-231 , 231 - 1] and [-263 ,
263 - 1], Respectively. Constants are written in base 10, for example, -666.
Decimal numbers are of type float or double. Constants are written in sci-
entific notation, for example 3.14159, 666 or 6.02E23.
Booleans are of type boolean. Constants are written as false and true.
Characters are of type char. Constants are written between apostrophes, for
example ‘b’.
To declare a variable of type T, replace the type int with T. The general
form of a declaration is thus {T x = t; p}.
4 1. Imperative Core
The operations for decimal numbers are +, -, *, /, along with some transcen-
dental functions: Math.sin, Math.cos, ...
For all data types, the expression (b) ? t : u evaluates to the value of t if
the boolean expression b has the value true, and evaluates to the value of u
if the boolean expression b has the value false.
Character strings are of type String. Constants are written inside quotation
marks, for example "Principles of Programming Languages".
let y = ref 4
in let x = ref 5
in let x = ref 6
in y := !x
and this program assigns the value 6 to the variable y, so it is the most recent
declaration of x that is used. We say that the first declaration of x is hidden by
the second.
Java, Caml and C allow the creation of variables with an initial value that
can never be changed. This type of variable is called a constant variable. A
variable that is not constant is called a mutable variable. Java assumes that
all variables are mutable unless you specify otherwise. To declare a constant
variable in Java, you precede the variable type with the keyword final, for
example
final int x = 4;
y = x + 1;
The following statement is not valid, because an attempt is made to alter the
value of a constant variable
final int x = 4;
x = 5;
In Caml, to indicate that the variable x is a constant variable, write let x
= t in p instead of writing let x = ref t in p. When using constant vari-
ables, you do not write !x to express its value, but simply x. So, you can write
let x = 4 in y := x + 1, while the statement let x = 4 in x := 5 is in-
valid. In C, you indicate that a variable is a constant variable by preceding its
type with the keyword const.
1.1.3 Sequence
1.1.4 Test
1.1.5 Loop
finite expression. And the fact that a loop may fail to terminate is a consequence
of the fact that it is an infinite object.
In Caml, this statement is written while b do p. In C, it is written as it
is in Java.
1.2.1 Input
Input constructs in Java are fairly complex, so we will use an extension of Java
created specially for this book: the class Ppl1 .
Evaluation of the expression Ppl.readInt() waits for the user to type a
number on her/his keyboard, and returns this number as the value of the
expression. A typical usage is n = Ppl.readInt();. The class Ppl also contains
the construction Ppl.readDouble which allows decimal numbers to be read
from the keyboard, and the construction Ppl.readChar which allows characters
to be read.
1.2.2 Output
Exercise 1.3
Write a Java program that reads an integer n from the keyboard, and
outputs a boolean indicating whether the number is prime or not.
We define an infinite set Var whose elements are called variables. We also define
the set Val of values which are integers, booleans, etc. A state is a function that
associates elements of a finite subset of Var to elements of the set Val.
For example, the state [x = 5, y = 6] associates the value 5 to the vari-
able x and the value 6 to the variable y. On the set of states, we define an
update function + such that the state s + (x = v) is identical to the state s,
except for the variable x, which now becomes associated with the value v. This
operation is always defined, whether x is originally in the domain of s or not.
We can then simply define a function called Θ, which for each pair (t,s)
composed of an expression t and a state s, produces the value of this expression
in this state. For example, Θ(x + 3,[x = 5, y = 6]) = 8.
This is a partial function, because a state is a function with a finite domain
while the set of variables is infinite. For example, the expression z + 3 has no
1.3 The Semantics of the Imperative Core 9
A state s is a function that maps a finite subset of Var to the set Val. It will be
helpful for the next chapter if we decompose this function as the composition
of two other functions of finite domains: the first is known as the environment,
which maps a finite subset of the set Var to an intermediate set Ref, whose
elements are called references and the second, is called the memory state, which
maps a finite subset of the set Ref to the set Val.
Var Ref Val
e m
This brings us to propose two infinite sets, Var and Ref, and a set Val of
values. The set of environments is defined as the set of functions that map a
finite subset of the set Var to the set Ref. The set of memory states is defined as
the set of functions mapping a finite subset of the set Ref to the set Val. For the
set of environments, we define an update function + such that the environment
e + (x = r) is identical to e, except at x, which now becomes associated with
10 1. Imperative Core
the reference r. For the set of memory states, we define an update function +
such that the memory state m + (r = v) is identical to m, except at r, which
now becomes associated with the value v.
However, constant variables complicate things a little bit. For one, the envi-
ronment must keep track of which variables are constant and which are mutable.
So, we define an environment to be a function mapping a finite subset of the
set Var to the set {constant, mutable} × Ref. We will, however, continue
to write e(x) to mean the reference associated to x in the environment e.
Then, at the point of execution of the declaration of a constant variable
x, we directly associate the variable to a value in the environment, instead of
associating it to a reference which is then associated to a value in the mem-
ory state. The idea is that the memory state contains information that can be
modified by an assignment, while the environment contains information that
cannot. To avoid having a target set for the environment function that is overly
complicated, we propose that Ref is a subset of Val, which brings us to pro-
pose that the environment is a function that maps a finite subset of Var to
{constant, mutable} × Val and the memory state is a function that maps
a finite subset of Ref to Val.
Var Val
Ref
a x b
Even though each label is associated with a unique reference, nothing prevents
two labels from being associated with the same reference, since an environment
is a function, but not necessarily an injective function. Finally, we represent
the memory state by filling each square with a value.
a x b
4 5
At first glance, this definition may seem circular, since to define the value
of an expression of the form t + u, we use the value of expressions t and u.
But the size of these expressions is smaller than that of t + u. This definition
is therefore a definition by induction on the size of expressions.
The first clause of this definition indicates that the value of an expression
that is a mutable variable is m(e(x)). We apply the function e to the variable x,
which produces a reference, and the function m to this reference, which produces
a value. If the variable is a constant variable, on the other hand, we find its
value directly in the environment.
The definition of the function Θ for Caml is identical, except in the case of
variables, where we have the unique clause
– Θ(x,e,m) = e(x),
where the variable x is either mutable or constant.
For example, if e is the environment [x = r] and m is the memory state
[r = 4] and that the variable x is mutable in e, the value Θ(x,e,m) is 4 in
Java, but is r in Caml.
Caml also has a construct ! such that
– Θ(!t,e,m) = m(Θ(t,e,m)).
If x is a variable, then the value of !x is Θ(!x,e,m) = m(Θ(x,e,m)) =
m(e(x)) that is the value of x in Java. This explains why we write y := !x +
1 in Caml, where we write y = x + 1; in Java.
In Caml, references that can be associated to an integer in memory are of
the type int ref. For example, the variable x and the value r from this example
are of the type int ref. In contrast to the variable x, the expressions !x, !x +
1, ... are of the type int.
The definition of the function Θ for C is the same as the definition used for
Java.
1.3 The Semantics of the Imperative Core 13
Exercise 1.4
Give the definition of the function Θ for expressions of the form t & u
and t | u.
Unlike the boolean operator & that evaluates its two arguments, the
operator && evaluates its second argument only if the first argument
evaluates to true. Give the definition of the function Θ for expressions
of the form t && u.
Answer the same question for the boolean operator ||, which only eval-
uates its second argument if the first argument evaluates to false.
– When the statement p is a test of the form if (b) p1 else p2 , the function
Σ is defined as follows. If Θ(b,e,m) = true then
14 1. Imperative Core
– This brings us to the case where the statement p is a loop of the form while
(b) q. We have seen that introducing the imaginary statement skip; such
that Σ(skip;,e,m) = m, we can define the statement while (b) q as a
shorthand for the infinite statement
When dealing with these types of infinite constructs, we often try to ap-
proach them as limits of finite approximations. We therefore introduce an
imaginary statement called giveup; such that the function Σ is never de-
fined on (giveup;,e,m). We can define a sequence of finite approximations
of the statement while (b) q.
p0 = if (b) giveup; else skip;
p1 = if (b) {q if (b) giveup; else skip;} else skip;
...
pn+1 = if (b) {q pn } else skip;.
The statement pn tries to execute the statement while (b) q by completing
a maximum of n complete trips through the loop. If, after n loops, it has not
terminated on its own, it gives up.
If isn’t hard to prove that for every integer n and state e, m, if Σ(pn ,e,m)
is defined, then for all n’ greater than n, Σ(pn ,e,m) is also defined, and
Σ(pn ,e,m) = Σ(pn ,e,m). This formalises the fact that if the statement
while (b) q terminates when the maximum number of loops is n, then it
also terminates, and to the same state, when the maximum number of loops
is n’.
There are therefore two possibilities for the sequence Σ(pn ,e,m): either it is
never defined, or it is defined beyond a certain point, and in this case, it is
constant over its domain. In the second case, we call the value it takes over
its domain the limit of the sequence. In contrast, the sequence does not have
1.3 The Semantics of the Imperative Core 15
a limit if it is never defined. We can now define the function Σ in the case
where the statement p is of the form while (b) q
Note that the statements pi are not always shorter than p, but if p contains
k nested while loops, pi contains k - 1. The definition of the function Σ is
thus a double induction on the number of nested while loops, and on the size
of the statement.
Exercise 1.5
What is the memory state Σ(x = 7;,[x = r],[r = 5])?
The definition of the function Σ for Caml is not very different from the
definition used for Java. In Caml, any expression that evaluates to a reference
can be placed to the left of the sign :=, while in Java, only a variable can appear
to the left of the sign =. The value of the function Σ of Caml for the statement
t := u is defined below:
– Σ(t := u,e,m) = m + (Θ(t,e,m) = Θ(u,e,m))
In the case where the expression t is a variable x, we have Σ(x := u,e,m)
= m + (Θ(x,e,m) = Θ(u,e,m)) = m + (e(x) = Θ(u,e,m)) and we end up
with the same definition of Σ used for Java.
The definition of the function Σ for C is not very different from the defini-
tion used for Java. The main difference is in case of variable declaration
Σ({T x = t; q},e,m) = (Σ(q,e+(x=r),m + (r = Θ(t,e,m))))|Ref−{r}
where r is a new reference that does not appear in e or m, and the notation
m|Ref−{r} designates the memory state m in which we have removed the ordered
pair r = v if it existed. Thus, if we execute the statement {int x = 4; p} q
in the state e, m, we execute the statement p in the state e + (x = r), m +
(r = 4) in C as in Java. In contrast, we execute the statement q in the state
e, m + (r = 4) in Java and in the state e, m in C.
As, in the environment e, there is no variable that allows the reference r
to be accessed, the ordered pair r = 4 no longer serves a purpose sitting in
memory. Thus, whether it is is left alone, as in Java or Caml, or deleted, as
in C, is immaterial. However, we will see, in Exercise 2.17, that this choice in
C is a source of difficulty when the language contains other constructs.
Exercise 1.6
The incomplete test allows the creation of a statement composed of a
boolean expression and a statement. This statement is written if (b)
p. The value of the function Σ for this statement is defined as follows. If
Θ(b,e,m) = true then
16 1. Imperative Core
Exercise 1.9
Give the definition of the Σ function for the declaration of a variable
without an initial value.
Exercise 1.10
Imagine an environment e — which cannot be created in Java — [x =
r, y = r], m, the memory state [r = 4], p, the statement x = x + 1;,
and m’, the memory Σ(p,e,m). What is the value associated with y in
the state e, m’? Answer the same question for the environment [x =
r1 , y = r2 ] and memory [r1 = 4, r2 = 4].
Draw these two states.
Exercise 1.11
Imagine that all memory states have a special reference: out. Define the
function Σ for the output construct System.out.print from the Section
1.2.
Exercise 1.12
In this exercise, imagine a data type that allows integers to be of any
size. To each statement p in the imperative core of Java, we associate the
1.3 The Semantics of the Imperative Core 17
System.out.print("Flight ");
System.out.print("819");
System.out.print(" to ");
System.out.print("Tokyo");
System.out.print(" takes off at ");
System.out.println("8:50 AM");
System.out.println();
System.out.println();
System.out.println();
System.out.print("Flight ");
System.out.print("211");
System.out.print(" to ");
System.out.print("New York");
System.out.print(" takes off at ");
System.out.println("8:55 AM");
System.out.println();
System.out.println();
System.out.println();
System.out.print("Flight ");
System.out.print("211");
System.out.print(" to ");
System.out.print("New York");
System.out.print(" takes off at ");
System.out.println("8:55 AM");
jumpThreeLines();
The statement jumpThreeLines(); that is found in the main program is
named the call of the function jumpThreeLines. The statement that is found in
the function and that is executed on each call is named the body of the function.
Organising a program into functions allows you to avoid repeated code,
or redundancy. As well, it makes programs clearer and easier to read: to un-
derstand the program above, it isn’t necessary to understand how the function
jumpThreeLines(); is implemented; you only need to understand what it does.
This also allows you to organise the structure of your program. You can choose
to write the function jumpThreeLines(); one day, and the main program an-
other day. You can also organise a programming team, where one programmer
writes the function jumpThreeLines();, and another writes the main program.
This mechanism is similar to that of mathematical definitions that allows
you to use the word ‘group’ instead of always having to say ‘A set closed under
an associative operation with an identity, and where every element has an
inverse’.
2.1 The Concept of Functions 21
2.1.2 Arguments
Some programming languages, like assembly and Basic, have only a simple
function mechanism, like the one above. But the example above demonstrates
that this mechanism isn’t sufficient for eliminating redundancy, as the main
program is composed of two nearly identical segments. It would be nice to
place these segments into a function. But to deal with the difference between
these two copies, we must introduce three parameters: one for the flight number,
one for the destination and one for the take off time. We can now define the
function takeOff
a = 3;
b = 4;
c = 5;
d = 12;
u = Math.sqrt(a * a + b * b);
v = Math.sqrt(c * c + d * d);
static int x;
static T1 x1 = t1 ;
...
26 2. Functions
static Tn xn = tn ;
However, in Java, the main program is placed inside a special function called:
main. The main function must not return a value, and must always have an
argument of type String []. In addition to the keyword static, the definition
of the main function must also be preceded by the keyword public.
In addition, the program must be given a name, which is given with the
keyword class. The general form of a program is:
class Prog {
static T1 x1 = t1 ;
...
static Tn xn = tn ;
For example
class Hypotenuse {
In C, the main program is also a function called main. For historical reasons,
the main function must always return an integer, and is usually terminated with
return 0;. You don’t give a name to the program itself, so a program is simply
a series of global variable and function declarations.
int main () {
printf("%f\n",hypotenuse(3,4));
return 0;}
class Prog {
static int n;
The value of the expression f(6) is 15. The function f adds the global vari-
able n, which has been initialised to 4 in the main program, the local variable
p, with a value of 5, and the argument x, with a value of 6.
In contrast, the value of the expression g(6) is 16, because both occurrences
of n refer to the local variable n, which has a value of 5. In the environment
in which the body of function g is executed, the global variable n is hidden by
the local variable n and is no longer accessible.
28 2. Functions
2.1.9 Overloading
In Java, it is impossible to define two functions with the same name, for example
static int f (final int x) {
return x;}
where
p0 = if (b) giveup; else skip;
and pn+1 = if (b) {q pn } else skip;.
32 2. Functions
– Finally, we add the case of functions, which is very similar to the case of
functions in the definition of the evaluation of expressions, except that if the
object Σ(p,e”,m”,G) has the form (normal,m”’), then we let
a b u x y
Next, we execute the body of the function, which produces the result
(return,5.0,m”) and so Θ(hypotenuse(a,b),e,m,G) is (5.0,m”). The result
of the execution of the statement u = hypotenuse(a,b); is then an ordered
pair composed of a boolean normal and the memory state m”’ = [r1 = 3.0,
r2 = 4.0, r3 = 5.0, r4 = 3.0, r5 = 4.0].
The value of the variable u in the state e, m”’ is 5.0.
2.2 The Semantics of Functions 33
Exercise 2.3
What happens if the formal arguments x and y of the function hypote-
nuse are constant variables?
Exercise 2.4
What happens if you execute the statement u = hypotenuse(a,b);,
with the variables a, b, and u declared in the main function?
Finally, we can give the definition of the Σ function for the entire program.
Let P be a program formed of global variable declarations static T1 a1 = t1 ;,
..., static Tn an = tn ; and of function declarations static U1 f1 (x1 ) p1 ;,
..., static Un fn (xn ) pn ;.
Let v1 , ..., vn be the initial values given to global variables, that is to say the
values of expressions ti . Let e be the environment [a1 = v1 , a2 = r2 , ...,
an = rn ] in which we associate the global variable ai to the value vi or to the
reference ri whether it is constant or mutable, and m is the memory state [r2
= v2 , ..., rn = vn ], in which we associate the references ri associated to
mutable global variables with the values vi . Let G be the global environment
(e, [f1 = (x1 ,p1 ), ..., fn = (xn ,pn )]).
The memory state Σ(P) is defined by
– Σ(P) = Σ(main(null);,e,m,G)
where null is a value of type String [] which we will discuss later.
Exercise 2.5
The function f is defined as follows
Since expressions can modify memory, consideration must be given to the fact
that in the definition of Σ we have given, arguments of a function are evaluated
from left to right. So, we evaluate t1 in the memory state m, and t2 in the
memory state m1 produced by the evaluation of t1 , ... So, the program
class Prog {
static int n;
2.2.4 Caml
The definition of the function Σ for Caml is somewhat different from the defini-
tion of Σ used for Java. In Caml, all formal arguments are constant variables,
so new references are never created at the point of a function call.
Also, in Caml, there is only one name space for functions and variables. In
Java, the program
class Prog {
static int f = 4;
static int x = 4;
let n = ref 0
in let f x y = x
in let g z = (n := !n + z; !n)
in print_int (f (g 2) (g 7))
2.2.5 C
The definition of the Σ function for C is also somewhat different from the
definition of Σ for Java.
In C, the references created at the moment of a function call are removed
from the memory and the end of the execution of the body of the function.
Like in Caml, there is only one name space for functions and variables, and
functions are declared in the same environment as variables. In this environ-
ment, we not only associate the name f to the list of formal arguments and the
body of the function, but also to the environment e to extend with the arguments
for executing the body of the function. This environment is, like in Caml, the
environment of the definition of the function. For example, the program
int f () {return x;}
int x = 4;
int main () {
printf("%d\n",f());
return 0;}
is invalid.
C compilers also evaluate a function’s arguments from left to right, as in
Java. However, the definition of the language, like that of Caml, does not specify
the order of evaluation of a function’s arguments, and it is up to the programmer
to write programs whose result does not depend on the order of evaluation.
Exercise 2.8
Give the definition of the Σ function for C, assuming that arguments are
always evaluated from left to right.
2.3 Expressions as Statements 37
class Prog {
static int a;
static int b;
a b x y
4 7 4 7
2.4 Passing Arguments by Value and Reference 39
The values of the variables x and y are exchanged, which results in the mem-
ory state [r1 = 4, r2 = 7, r3 = 7, r4 = 4, r5 = 4] which returns control
to the main program. The environment is then e = [a = r1 , b = r2 ] with
the memory state [r1 = 4, r2 = 7, r3 = 7, r4 = 4, r5 = 4]. The values
of the variables a and b have not changed.
In other words, the function swap ignores the variables a and b. It can only
use their value at the moment of the function call, and cannot modify their
value: executing the statement swap(a,b); has the same result as executing
the statement swap(4,7);.
The mechanism of argument passing that we have described is called argu-
ment passing by value. It does not allow the creation of a swap function that
changes the contents of two variables. However, most programming languages
have a construct that allows the creation of such a function. But, this construct
is somewhat different in each language. Before seeing how this is done in Java,
Caml, and C, we will look at the much simpler example of the Pascal language.
2.4.1 Pascal
a x b y
4 7
Because of this, the procedure swap exchanges the contents of the references r1
and r2 and not of the references r3 and r4
a x b y
7 4
and after execution of the procedure, the contents of the references associated
with the variables a and b have been exchanged.
Being able to explain the mechanism of passing by reference is the main
motivation for decomposing the state into an environment and a memory state
by introducing an intermediate set of references, as we have done in the previous
chapter.
Exercise 2.10
Give the definition of the Σ function in the case of functions with an
argument passed by reference.
2.4.2 Caml
a:= 4;
b := 7;
swap a b;
print_int !a;
print_newline();
print_int !b;
print_newline()
Indeed, when we call the function swap a b in the environment [a = r1 , b =
r2 ] and the memory state [r1 = 4, r2 = 7], we create the environment [a =
r1 , b = r2 , x = r1 , y = r2 ] in which the constant formal arguments x and
y are linked to the real arguments r1 and r2 and we keep the same memory
state [r1 = 4, r2 = 7]
a x b y
4 7
and the function swap exchanges the contents of the references r1 and r2 and
after the execution of the function, the contents of the references associated
with the variables a and b have now also been exchanged.
Exercise 2.11
What does the following program do?
2.4.3 C
int main () {
int x;
int* u;
x = 4;
u = &x;
printf("%d\n",*u);
return 0;}
u x
a x b y
4 7
And, the function swap exchanges the contents of the references r1 and r2 and
after the execution of the function, the contents of the references associated
with the variables a and b have been exchanged.
In this example, take note of the syntax of the declaration of the argu-
ment x, int* const x, which prevents the assignment x = t; but allows the
assignment *x = t;. The declaration const int* x, in contrast allows the as-
signment x = t; but prevents the assignment *x = t;. The declaration const
int* const x prevents both types of assignment.
Exercise 2.14
What is the output of the following program?
*x = *y;
*y = z;}
int main () {
a = 4;
b = 7;
swap(&a,&b);
printf("%d\n",a);
printf("%d\n",b);
return 0;}
Exercise 2.16
Give the definition of the Θ function for expressions of the form *t and
&x, and the definition for the Σ function for statements of the form *t
= u;.
Exercise 2.17
The goal of this exercise is to demonstrate that, in C, you may look for
a reference that does not exist.
1. In the following Caml program, what is the state in which the state-
ment print_int !(!u) is executed?
let f p = let n = ref p in let x = ref n in !x
in let u = ref (f 5)
in print_int !(!u)
Answer the same question for the following program.
let f p = let n = ref p in let x = ref n in !x
in let u = ref (f 5)
in let v = ref (f 10)
in print_int !(!u)
2.4 Passing Arguments by Value and Reference 45
int main () {
int* u = f(5);
printf("%d\n",*u);
return 0;}
In what state is the statement printf("%d\n",*u); executed?
Hint: remember that in C, in contrast to Caml, we remove from
memory the reference associated with a variable when that variable
is removed from the environment.
3. In C, when we use a reference that is not declared in memory, it
does not produce an error, and the result will be unpredictable. Try
compiling and running the following C program.
int main () {
int* u = f(5);
int* v = f(10);
printf("%d\n",*u);
return 0;}
2.4.4 Java
and the definition of the function that multiplies its argument by 4 or that
squares it would be identical.
Another way to try to understand the definition of the function fact is to see
it as a definition by induction of the sequence un = n!: u0 = 1, un+1 = (n +
1) * un . Although this works for this function, that does not mean it will work
in general, for example for the function
When we have a recursive function definition, for example the definition of the
factorial, it is possible to transform this definition into another, non-recursive
one, by replacing the calls of the function fact in the body of the function
50 3. Recursion
fact by calls to another function fact1, identical to fact, but defined before
it
static int fact1 (final int x) {
if (x == 0) return 1;
return x * fact1(x - 1);}
we start by defining (v1 ,m1 ) = Θk (t1 ,e,m,G), (v2 ,m2 ) = Θk (t2 ,e,m1 ,G), ...,
(vn ,mn ) = Θk (tn ,e,mn−1 ,G), then e” and m” as we have done in the previous
chapter. Next, instead of considering the object Σk (p,e”,m”,G) we consider
the object Σk−1 (p,e”,m”,G). A notable exception occurs in the case where k
= 0, and in this case, the Σ0 function is not defined for this expression.
Once the family of Σk functions is defined, we define the Σ function
Σ(p,e,m,G) = limk Σk (p,e,m,G).
f = (x → f(x))
f = (x → 2 * f(x))
What does the call fact(-100) return if we define the function fact as
follows?
Why? And what does the call fact(-100) return if we define the function
fact as follows?
Why?
3.3 Caml
In Caml, we execute the body of the function in the environment in which the
function was declared, extended by the declaration of its arguments. Because of
this fact, only functions declared before the function f are accessible within the
body of f, and the declaration
let fact x = if x = 0 then 1 else x * fact(x - 1)
in print_int (fact 6)
is invalid.
To be able to use the function fact within its own definition, you must use
a new construct let rec f x1 ... xn = t in p
let rec fact x = if x = 0 then 1 else x * fact(x - 1)
in print_int (fact 6)
and, at each function call, the definition of the function fact is then added to
the environment in which the body of the function is executed.
When two functions are mutually recursive, you cannot declare them as
follows
let rec even x = if x = 0 then true else odd(x - 1)
in let rec odd x = if x = 0 then false else even(x - 1)
in print_bool(even 7)
54 3. Recursion
because the environment in which the function even is executed does not contain
the function odd, so we have to use a special construct for mutually recursive
functions let rec f x1 ... xn = t and g y1 ... yp = u and ... For ex-
ample
let rec even x = if x = 0 then true else odd (x - 1)
and odd x = if x = 0 then false else even (x - 1)
in print_bool (even 7)
3.4 C
In C, as in Caml, the body of the function is executed in the environment in
which this function was declared, extended with the declaration of its arguments.
Because of this, only functions declared before a function f are accessible in the
body of f.
However, in order to allow for recursion, at each function call, the definition
of the function f is added to the environment in which the body of the function is
executed. This is exactly what happens in Caml with let rec, so the declaration
of a function in C is more like Caml’s let rec than it is like let. Therefore,
the following program
int fact (const int x) {
if (x == 0) return 1;
return x * fact(x - 1);}
int main () {
printf("%d\n",fact(6));
return 0;}
is valid, and returns 720.
When the definitions of several functions, for example two functions f and
g, are mutually recursive, we must start by prototyping the function g to allow
g to be called within the body of f. Prototyping a function defines its argument
types and its return type, and you can then define the actual function later in
the program.
int main () {
printf("%d\n",even(7));
return 0;}
r = 1;
for (i = 1; i <= x; i = i + 1) {r = r * i;}
return r;}
and recursively
static int fact (final int x) {
if (x == 0) return 1;
return x * fact(x - 1);}
we see that the first uses assignments: r = 1;, i = 1;, i = i + 1; and r = r
* i;, while the second does not. It is therefore possible to program the factorial
function without using assignments.
More generally, we can consider a sub-language of Java in which we remove
assignment. In this case, all variables can be declared as constant and, in the
definition of the Σ function, the memory state is always empty. Sequences and
loops in this case become useless. We are left with a shell of Java composed
of variable declarations, function calls, arithmetical and logical operations and
tests. This sub-language is called the functional core of Java. We can also define
the functional core of many programming languages.
Surprisingly, this functional core is just as powerful as Java as a whole.
For each expression t of Java, we associate the partial function that maps
the integer n to the value v such that (v,m’) = Θ(t,[x = n],[],G), and
for each statement p in Java we associate the partial function that maps the
integer n to the value v such that (return,v,m) = Σ(p,[x = n],[],G). A
56 3. Recursion
The only movement allowed is to move a single disk from the top of one
column to the top of another column, with the condition that you can
never place a bigger disk on top of a smaller one. We write n -> n’ the
movement of a disk from column n to column n’. The goal of the game
is to move all of the disks from the left column to the right column.
This curve is defined as the limit of the sequence of curves where the
first curve is a segment
and where each element is obtained from the previous one, by dividing
each segment in 3, and replacing the middle segment with an equilat-
eral triangle with one side removed. The second iteration of the Koch
snowflake is thus
the third
58 3. Recursion
In the programs that have been described in previous chapters, each variable
contained an integer, a decimal number, a boolean, or a character.
These variables could not contain an object composed of several numbers,
booleans or characters, such as a complex number made of two decimal num-
bers, a vector made of several coordinates, or a string made of several charac-
ters.
We will now introduce a new construct, records, that allow the construction
of these composite objects.
In Java, we define a new type of records by indicating the label and type of
each of its fields. For example, the type Point is defined as
class Point {
final double latitude;
final double longitude;
final double altitude;}
This definition is written before the introduction of the name of the program
with the keyword class.
Once this type is defined, you can declare a variable with the type Point.
Point x;
As with any variable declaration, this adds an ordered pair to the environ-
ment that associates a reference r to this variable, and an ordered pair to the
memory that associates a value to the reference r. If we declare this variable
without giving it a value, the value by default is a special value called null.
We represent a state, in which the variable x is associated in the environment
to a reference r, which is associated in the memory to the value null this way.
longitude = 0.0, altitude = 0.0}. The value of this expression is the ref-
erence r’.
In this diagram, the object on the right is only one box that can contain three
decimal numbers, and not a collection of three boxes. Seen another way, there
is only one reference r’.
A reference that was added to memory by the construct new is called a cell.
The set of memory cells is called the heap. The operation that adds a new cell
to the memory state, is called an allocation.
When you execute the statement
x = new Point();
you associate the reference r’ to the reference r in memory. The environment
is then [x = r] and the memory state [r = r’, r’ = {latitude = 0.0,
longitude = 0.0, altitude = 0.0}].
In Java, as in Caml and C, references are values and it is possible for a reference
to be associated to another reference. But unlike Caml and C, this can only be
done using this construct for records.
It is possible to declare the variable x with an initial value. Instead of writing
Point x;
x = new Point();
you can write
Point x = new Point();
Like any variable, a variable of type Point can be mutable or constant. So,
whereas the statement
62 4. Records
In the definition of a record, it is also possible that the fields are mutable
class Point {
double latitude;
double longitude;
double altitude;}
4.1 Tuples with Named Fields 63
But, this is not the case: you have already introduced a reference r’ by creating
the cell and you can use this to make fields mutable without introducing extra
references. Whether the fields are constant or mutable, you are constructing
the environment [x = r] and the memory state [r = r’, r’ = {latitude
= 0.0, longitude = 0.0, altitude = 0.0}]
In this cell, you simply choose which fields are mutable and which are constant.
To assign to a record’s mutable field, you use a new statement t.l = u;,
where l is a label and t and u are expressions. If the value of expression t
is a reference r associated in memory with a record that has a field l, then
when you execute the statement t.l = u;, the field l of this record receives
the value of expression u.
Thus, in executing the statements
x.latitude = 48.715;
64 4. Records
x.longitude = 2.208;
x.altitude = 156.0;
you create the state
4.1.5 Constructors
class Point {
double latitude;
double longitude;
double altitude;
Exercise 4.1
Define the Σ function for the four operations: type definition, cell allo-
cation, accessing to a field, assigning to a field.
4.2 Sharing
4.2.1 Sharing
Suppose that x and y are two variables of type Point, associated with two
references r1 and r2 in the environment. In addition, suppose that, in mem-
ory, r1 is associated with a reference r3 , which itself is associated with a record
{latitude = 48.715, longitude = 2.208, altitude = 156.0} and r2
with a reference r4 , which itself is associated with the record {latitude
= 90.0, longitude = 0.0, altitude = 0.0}. So, e = [x = r1 , y = r2 ],
m = [r1 = r3 , r2 = r4 , r3 = {latitude = 48.715, longitude = 2.208,
altitude = 156.0}, r4 = {latitude = 90.0, longitude = 0.0, altitude
= 0.0}].
4.2.2 Equality
If a and b are two expressions of type Point, their value is a reference and
the expression a == b evaluates to true only when these two references are
identical. That is to say when a and b share the same cell. We call this type of
equality physical. So, the program
Point a = new Point(48.715,2.208,156.0);
Point b = new Point(48.715,2.208,156.0);
if (a == b) System.out.println("equal");
else System.out.println("different");
outputs different.
It is also possible to write a function that tests the structural equality of
two records, which checks the equality of their fields
static boolean equal (final Point x, final Point y) {
return (x.latitude == y.latitude)
&& (x.longitude == y.longitude)
&& (x.altitude == y.altitude);}
and the statement
if (equal(a,b)) System.out.println("equal");
else System.out.println("different");
outputs equal.
A wrapper is a type of record with one lone field. An example is the type
Integer
class Integer {
int c;
Integer (int x) {
this.c = x;}}
At first glance, the Integer type and the int type may seem redundant, be-
cause the record {c = 4} is not very different from the integer 4. And it is true
that the program
Integer x = new Integer(4);
Integer y = new Integer(x.c);
4.2 Sharing 69
x.c = 5;
System.out.println(y.c);
can be rewritten as
int x = 4;
int y = x;
x = 5;
System.out.println(y);
which produces the same result: both output the number 4.
However, if you replace the statement Integer y = new Integer(x.c);
with Integer y = x; instead of obtaining the state
In Java, it is not possible to write a function that swaps the contents of two
arguments of type int. However, it is possible with arguments of type Integer.
static void swap (Integer x, Integer y) {
int z = x.c;
x.c = y.c;
y.c = z;}
and the program
outputs 7 and 4.
Wrapper types allow you to simulate passing arguments by reference in
Java.
Indeed, when you call the function swap, you create the state
72 4. Records
and swapping the contents of x and y will also swap those of a and b.
If variables a, b, x and y are constant and of the type Integer, then the
state created is as follows
a x b y
4 7
Exercise 4.2
In which state is the body of the function swap executed if a and b are
constant, but x and y are mutable? And if a and b are mutable, but x
and y are constant?
Exercise 4.3
What does the following program output, if you replace the function
swap with the function swap1 defined below?
4.3 Caml
We will now see constructs in Caml that allow you to define a type, allocate a
cell, read a field, and assign to a field.
In Caml, as in Java, you define a record type by defining the type and label of
each of its fields.
type point = {
latitude : float;
longitude : float;
altitude : float;}
You create a value to assign to each of its fields {latitude = 90.0; longitude
= 0.0; altitude = 0.0;}. There is no new keyword, and the pair of braces
indicates that you want to allocate a cell.
The declaration let x = ref {latitude = 90.0; longitude = 0.0;
altitude = 0.0;} creates the state
74 4. Records
In Caml, all fields are constant by default, and you must indicate that they
should be mutable with the keyword mutable.
type int_wrap = {mutable c : int}
Mutable field assignment is written as x.c <- 4.
The function
4.3 Caml 75
a b
4 7
the values associated with variables x and y are the references r and r’ and
not the integers 4 and 7
a x b y
4 7
type point = {
mutable latitude : float;
mutable longitude : float;
mutable altitude : float;}
type point = {
76 4. Records
4.4 C
4.4.1 Definition of a Record Type
In C, we define a record type, called a structure, by defining the label and type
of each of its fields
struct Point {
double latitude;
double longitude;
double altitude;};
A variable of type Point can never have the value null. It directly asso-
ciates, in the environment, the variable x with a reference r and, in memory, the
reference r to the record {latitude = 90.0, longitude = 0.0, altitude =
0.0}. Therefore, there is one less level of indirection than is found in Java.
x y
tropic(&x);
The statement
struct Point x = {90.0,0.0,0.0};
struct Point* y = &x;
creates the state
y x
Exercise 4.5
What does the following program output?
struct Integer {
int c;};
int main () {
struct Integer a = {4};
struct Integer b = {7};
swap(a,b);
printf("%d\n",a.c);
return 0;}
4.5 Arrays
Programming languages allow you to also create tuples where the fields are not
named, but are identified by number. We call them arrays.
The fields of an array, in contrast to those of a record, are all of the same
type. The number of fields of an array is determined during the allocation of
the array, and not during the declaration of its type, as is the case with records.
This allows you to compute, during the execution of a program, an integer n and
to allocate an array of size n. It is also possible to compute, during execution
of a program, an integer k and to access the kth field of an array, or to assign
to it.
However, once an array is allocated, it is not possible to change its size. The
only way to accomplish this is to allocate a new array of a different size and to
copy the fields of the old one to the new one.
Let
71
10104 16 20
10104 4
^ = 10−104 ( (−1)i
π 2i+1
− (−1)i 2i+1
)
i=0
5 (2i + 1) i=0
239 (2i + 1)
^ | ≤ 10−101 .
|π − π
In order to represent tuples indexed with ordered pairs or ordered triplets of in-
tegers, such as matrices, one possibility is to create an array whose elements are
themselves arrays. The element at index (i,j) of array t is written t[i][j].
Such an array has the type T [][].
Allocation of such an array is done with a single operation
int [][] t = new int [20][20];
Exercise 4.7
The game of life is a game invented by John Conway in 1970. On a
grid, you lay out creatures randomly. The population of these creatures
evolves from one state to the next based on the following rules.
– A creature survives if it has 2 or 3 neighbours in the 8 adjacent cells,
and it dies due to isolation or overpopulation otherwise.
– A creature is born in an empty cell if it has exactly 3 neighbours in
the 8 adjacent cells, and no creature is born in that cell otherwise.
For example, starting with the initial state
4.5 Arrays 83
Write a program that implements the game of life, so you can observe
the development of a population of creatures.
In Caml, an array with elements of type T is of the type T array. You allocate
an array with the expression Array.make u v where u is an expression whose
value is an integer n and v is an expression of type T. The array that is created
has size n and each field receives the value of expression v as its initial value.
If the value of expression t is a reference associated in memory with an
array and the value of expression u is an integer k, the value of the expression
Array.get t u is the value contained in the kth field of this array.
To assign to the kth field of an array, you use the new statement Array.set
t u v where t is an expression whose value is a reference associated in memory
with an array, u and an expression whose value is an integer k, and v is an
84 4. Records
expression of the same type as the elements of the array. When you execute
this statement, the kth field of the array receives the value of expression v.
So, the program
let t = Array.make 10 0
in let k = 5
in Array.set t k 4; print_int (Array.get t k)
outputs 4.
4.5.6 Arrays in C
In C, arrays, like records, are not allocated. Because of this fact, the size of an
array cannot be determined at the moment of its allocation, and is part of its
type.
An array of size n whose elements are of type T are of type T[n]. You declare
a variable t of type T[n] as follows
T t [n];
For example
int t [10];
The size of an array is not necessarily constant, but can be any integer expres-
sion. The array created has size n and each field receives an unspecified initial
value.
In C, as in Java and Caml, the value of the variable t is a reference asso-
ciated in memory with an array. The arrays are therefore somewhat different
from records. If the value of expression t is a reference associated in memory
with an array and the value of expression u is an integer k, the value of ex-
pression t[u] is the value contained in the kth field of this array. To assign
to the kth field of an array, you use a new statement t[u] = v where t is an
expression whose value is a reference associated in memory with an array, u is
an expression whose value is an integer k and v is an expression of the same
type as the elements in the array. When you execute this statement, the kth
field of the array receives the value of expression v. So, the program
int t [10];
int k = 5;
t[k] = 4;
printf("%d\n",t[k]);
outputs 4.
5
Dynamic Data Types
In the previous chapter, we created a record type whose fields were of the
scalar type double. It is also possible to define a record type T whose fields
are themselves records, in particular the type T itself. For example, a triplet
of integers (a,b,c) can be defined as the pair (a,(b,c)), and more generally
a non-empty list of integers can be defined as an ordered pair composed of an
integer, the head of the list, and a shorter list, the tail of the list. Thus, the
type List is defined as follows
class List {
int hd;
List tl;}
The head of the list 1, 2, 3, 4, for example, is the integer 1. The tail of this
list is the list 2, 3, 4 — and not the integer 4.
This definition is not complete, because if List is the type of lists, the cartesian
product int × List contains only non-empty lists. The type List is therefore
not the cartesian product int × List, but the disjoint union {empty}
(int
× List).
However, since a value of the type List is either null or a reference asso-
ciated in memory with a record, the type List that we have defined is equal
to {null}
(int × List), so that the empty list and the value null get
identified.
5.1.3 An Example
then the statement l = new List(); allocates a cell r’, fills this cell with the
default values 0 and null, and assigns the reference r’ to the variable l
The statement l.hd = 4; assigns the value 4 to the field hd of the cell
4 0
Then, the last two statements assign to the fields of this cell
88 5. Dynamic Data Types
4 5
In the previous section we have ‘defined’ the type List, as List = {null}
(int × List). The fact that List appears on the right side of the = sign
means that its definition is recursive. Again, recursive definitions appear to be
circular, and one way to avoid this circularity is to read the proposition List
= {null}
(int × List), not as a definition, but as an equation. The type
List is therefore defined as the solution to the equation List = {null}
(int × List). This calls into question the existence and the uniqueness of
this solution.
To construct a solution we proceed, as was the case with recursive functions,
by successive approximations, by defining the set Li of values of type List that
we can construct in at most i steps.
L0 = ∅
L1 = {null}
(int × L0 ) = {null}
L2 = {null}
(int × L1 )
L3 = {null}
(int × L2 )
...
Then, we define the type List as the limit of this sequence
List = i Li
5.1 Recursive Records 89
The value null is essential to this process. The same construction with the
equation List = int × List gives an empty type.
Note that the type created this way is not a unique solution to the equation
List = {null}
(int × List). The set of all the finite or infinite sequences
is also a solution. But the solution created above is the smallest set that is a
solution to this equation.
More generally, if you consider a set E, a family of sets A1 , ..., An and a
family of functions f1 , ..., fn such that fi is an injective function mapping
Ai × Emi to E and the images of fi are disjoint, you can construct the small-
est subset of E closed by the functions fi . This allows you to understand all
recursive type definitions.
Among these recursive types, some have only one recursive field and others
have several. We call types with one recursive field list types, regardless of the
number of non-recursive fields. We call types with several recursive fields tree
types.
A list is called regular if it has a finite number of sub-lists. For example, the
list 4, 4, 4, 4, 4, ... is regular, as is the list 4, 5, 4, 5, 4, 5, 4, 5,
4, 5, ... or the list 1, 2, 3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, .... but
the list 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, ... is not. It isn’t diffi-
cult to show that a list is regular if it is either finite, or infinite but ultimately
periodic, and that a list of digits of a real number is regular if this number
is rational. We can show that the set of regular lists is also a solution to the
equation List = {null}
(int × List). It is this set, and not the set of
finite lists, that the declaration of the type List defines. The minimal number
of cells for constructing a list is the number of its distinct sub-lists.
This notion of regular lists can be generalised to any recursive type, and we
can show that the values of such a type are regular.
Some programmers choose to never create infinite objects. Some program-
ming languages do not even allow the creation of such objects. For example, if
you add to Java the binary constructor for lists, you cannot construct infinite
lists, because in an expression new List(x,l); the object l must already be
created.
It is important to know which convention is in use: allowing or prohibiting
the construction of infinite objects, because the function
static int sum (final List l) {
if (l == null) return 0;
return l.hd + sum(l.tl);}
terminates properly if you apply it to finite lists, but enters an infinite loop if
you try to apply it to infinite lists.
Exercise 5.2
Write a function that tests whether a regular list is finite or infinite.
e + 0 −→ e
0 + e −→ e
e × 0 −→ 0
0 × e −→ 0
e × 1 −→ e
1 × e −→ e
Exercise 5.6
Write a function that tests the equality of two arithmetical expressions.
Exercise 5.7
Define a type for large integers, represented as a list of digits, each digit
being of type int. Program the four basic operations for these integers.
5.4 Caml
In Caml, you can define a recursive record type, for example
type list = {hd : int; tl : list;}
However, as there is no null value, the smallest solution of this definition
is empty. Thus these types contain only infinite values.
5.4 Caml 93
We must therefore specify the disjoint union between the singleton {empty}
and the cartesian product int × list explicitely. A solution is to use a selector
field
type list = {select : bool; hd : int; tl : list;}
but a default value for the type list is required in the case of the empty list,
and again, there is no null value in Caml. The only solution would be to use
an infinite default value, which would not be very elegant.
To solve this problem, Caml has a primitive construct that allows the con-
struction of disjoint unions of cartesian products. The disjoint union of the
types T11 × ... × T1n1 , ..., Tp1 × ... × Tpnp is defined by
type T = C1 of T11 * ... * T1n1 | ... | Cp of Tp1 * ... * Tpnp
where C1 , ..., Cp are constants that replace the selector field values, and that are
called constructors, although they have little to do with constructors in Java.
For example, the disjoint union of a singleton — empty cartesian product —
and of the cartesian product int × list is defined below
Cons
4
Cons 5
Nil
94 5. Dynamic Data Types
5.5 C
In Java, the value of an expression of a record type is a reference associated in
memory with a record. In C, in comparison, the value of such an expression is
the record itself. Because of this, in C, there is no value null and a recursive
record type like
struct List {
int hd;
struct List tl;};
is empty, as a value of this type would contain a field hd and a field tl that
would itself contain a field hd and a field tl, ... The definition of such a type
is thus prohibited in C.
The solution to define a type of lists is, as in Java, that the value of the
record’s second field is, not a record, but a reference associated in memory with
a record. But what is implicit in Java must be explicitly stated in C.
struct List {
int hd;
struct List* tl;};
In Java, the contents of the field tl can be either null, or a reference. In
C, it is always a reference, but there is a special reference called NULL that can
never be associated in memory and that plays the same role as null in Java.
However, this NULL construct is not part of the fragment of the language that
handles records, but is part of the fragment that handles references, like the
constructs &, *, ... We can therefore construct the following singleton list
struct List l = {3,NULL};
Note that the state constructed in C is
3 4
3 4
in which the variable l is, not of type List, but of type List*.
struct List* l;
l = (struct List *) malloc (sizeof(struct List));
(*l).hd = 3;
96 5. Dynamic Data Types
1 2
3 4
when you execute the statement y = null;, we associate the value null with
the reference r4 . The memory state now becomes m’ = [r1 = r2 , r2 = {hd =
1, tl = r3 }, r3 = {hd = 2, tl = null}, r4 = null, r5 = {hd = 3, tl
= r6 }, r6 = {hd = 4, tl = null}]
5.6 Garbage Collection 97
1 2
3 4
1 2
It is then the reference r6 that can be removed from memory that becomes
m”’ = [r1 = r2 , r2 = {hd = 1, tl = r3 }, r3 = {hd = 2, tl = null},
r4 = null]
98 5. Dynamic Data Types
1 2
It’s not very hard to show that the presence or the absence of these two
cells in memory has no observable effect. That is, the execution of any program
will produce the same results whether these cells are present in memory or not,
with the condition that the set of references is infinite.
Each of these cells occupy a certain number of elements in the physical
memory of the computer on which the programme is run, that is always finite,
and we may want to remove them to economise this memory space: if we did
not recycle the physical memory of the computer, we would quickly run out of
memory.
Some languages, like Java and Caml, have an automated system of collecting
these cells: garbage collection (GC).
Other older languages, like C, do not have garbage collection. The memory
must therefore be managed by hand. This requires the addition of the references
r5 and r6 to a list of free cells before issuing the statement y = NULL;.
5.6 Garbage Collection 99
1 2
3 4
free
Then, each time you require a new cell, instead of allocating systematically, you
can test the free list first to see if it contains a cell, and if it does, you can
recycle it.
When we free a cell, care must be taken that the cell is not still accessible
because it was previously shared. If we execute the statement y = NULL; in
the state e = [x = r1 ,y = r4 ], m = [r1 = r2 , r2 = {hd = 1, tl = r3 },
r3 = {hd = 2, tl = NULL}, r4 = r5 , r5 = {hd = 3, tl = r3 }]
1 2
1 2
in which we can free the cell r5 but not the cell r3 , which appears not only in
the pair associated with the reference r5 but also in the pair associated with the
reference r2 .
This requires that information about the number of times that the reference
r3 is used in memory be available in the cell r3 . Therefore, we add to each
cell a field that contains an integer indicating the number of occurrences of
this cell in the memory and the environment. This field is called a reference
counter. In the above example, the reference counter of the cell r5 is 1 and the
reference counter of the cell r3 is 2. Before executing the statement y = NULL;
we decrement the reference counter of r5 which becomes 0, which means that
the cell can be collected. Collecting this cell decrements the reference counter of
r3 which becomes 1, but this cell is not collected.
The C language contains a statement free that manages a list of free cells
automatically, but the decision to free a cell or not is always up to the program-
mer.
In some languages, like Java and Caml, garbage collection is automatic, and
the programmer does not have to worry about it. Local methods, like using a
reference counter, were used by early languages as a means of managing the
memory, but they have been replaced by global methods, like using a marking.
At some point, the execution of the program stops and all its accessible
references in the environment are recursively marked with a star
5.6 Garbage Collection 101
* * *
1 2
3 4
then the physical memory of the computer is scanned, all the non-marked cells
are collected and the execution of the program resumes.
An advantage of these global methods is that they only require the addition
of a boolean to each cell, and not the addition of an integer for reference
counting. Also, they allow you to free more cells when using infinite values. For
instance, in the following state
* * *
1 2
3 4
only one cell is collected using a reference counter, but two are with the markup
method.
102 5. Dynamic Data Types
Garbage collection has a subtle interaction with function calls. Indeed, when
you start the garbage collection during the execution of a function, it is pos-
sible that some variables are not in the current environment but will return
to the environment when the function returns. This typically occurs with local
variables of the main program.
The initial set of references to mark is not only the set of references acces-
sible in the current environment, but the set of accessible references of all the
environments of functions in which you may return after finishing execution of
the current function.
6
Programming with Lists
Imagine that all the libraries in a city or a country decide to consolidate their
catalogs together and write a program that allows you to know which book is
in which library, allowing for inter-library loans.
The users of this program have the option of asking whether a certain book
is in a certain library or not. For this, a function must be written that takes a
character string s and a list of character strings b as arguments, and returns
the boolean value true or false depending on whether the string s appears in
the list b or not.
This function can be written as follows
static boolean mem (String s, List b) {
if (b == null) return false;
if (equal(s,b.hd)) return true;
return mem(s,b.tl);}
Then, you can define a list
List b = new List("Discourse on the Method",
new List("Critique of Pure Reason",
new List("Principles of Programming Languages",
null)));
An association list is a list of pairs that is functional, meaning that for each k,
there exists at most one element v such that the pair (k,v) appears in the list.
The first element of a pair in the list is called a key and the second is called a
value. Association lists are a means of representing functions of a finite domain,
like dictionaries.
6.2 Concatenation: Modify or Copy 105
Exercise 6.3
Write a function that searches for a key in an association list and returns
the value associated with this key.
Write a function that updates an association list, so that the value as-
sociated with a key is updated if it already exists, and is added if not.
Imagine that two libraries decided to merge. To create the new library’s catalog,
a new list must be created that contains the elements from both libraries. This
operation, which begins with two lists x1 , ..., xn and y1 , ..., yp , creates
the list x1 , ..., xn , y1 , ..., yp is called concatenation. To simplify our
diagrams, we will consider lists of characters instead of lists of character strings.
Concatenation can be programmed as follows
static List append (List x, List y) {
if (x == null) return y;
List p = x;
while (p.tl != null) p = p.tl;
p.tl = y;
return x;}
If you define two lists
List b = new List(’a’,new List(’p’,new List(’p’, null)));
List c = new List(’e’,new List(’n’,new List(’d’, null)));
and you output their concatenation
printList(append(b,c));
you obtain the list
a p p e n d
Indeed, in the case where the list x is not empty, the statement p = x;
starts by putting the first element of the list x in the variable p
106 6. Programming with Lists
b p
a p p
e n d
Then the statement while (p.tl != null) p = p.tl; shifts this reference
in such a way that the variable p contains the last cell in the list
6.2 Concatenation: Modify or Copy 107
b p
a p p
e n d
Then, the statement p.tl = y; replaces the field tl of this cell by the first
cell in the list y
108 6. Programming with Lists
b p
a p p
e n d
And finally the statement return x; returns the first cell of the list x. The
value returned by the function is then the first cell of a list whose elements are
a p p e n d.
However, this function is not perfect, because if, after the computation, you
output the list b and the list c you obtain e n d for the list c but a p p e n
d and not a p p for the list b. As you can see in the figure, the variable b also
contains the first cell of a list whose elements are a p p e n d.
In addition, the result of this function is not the concatenation of its two
arguments when both lists have elements in common. When you concatenate
b and b for example, you place the first cell of the list b in the field tl of the
last cell of this list and you construct an infinite list. The statement
printList(append(b,b));
outputs
a p p a p p a p p a p p ...
6.2 Concatenation: Modify or Copy 109
Thus, this function has the benefit of not allocating extra cells, which is
desirable when the lists are very long, but at the expense of modifying its
arguments, and not being correct when its arguments share cells.
Exercise 6.4
How many distinct sublists does the list a b c have? What about the
list a b c a b c a b c a b c a b c ...? And the list a b c a b c?
Show that if f is an arbitrary function that does not allocate memory,
and that l is the list a b c then f(l,l) cannot return the list a b c a
b c.
6.2.2 Copy
b p
a p p
a p p
e n d
and the variable b keeps the value a p p. The advantage of this function is
that it does not alter its arguments and that it can be applied to any two lists,
even if they have elements in common. However, it does this with the added
6.2 Concatenation: Modify or Copy 111
Instead of using a while loop, you can program concatenation by copying its
first argument recursively
static List append (List x, List y) {
if (x == null) return y;
return new List(x.hd, append(x.tl,y));}
In this case, assignment is not used and the function is written in the functional
core of Java.
Exercise 6.6
Program a function that removes an element from a list, by copying its
argument. Create another function that does the same, but by modifying
its argument.
Exercise 6.7 (Sieve of Eratosthenes)
Consider lists of pairs formed of an integer and a boolean that indicates
whether this integer is marked or not.
1. Write a function that marks all the elements in positions that are
non-trivial multiples of an integer n in a list l.
2. Write a function that returns the list of integers between 2 ... 1000
none of them being marked.
3. Write a function that returns the list of prime numbers below 1000.
Exercise 6.8
If T is a type that can be mathematically ordered, such as integers, we
say that a list of elements of type T is ordered if each element of this list
is of lesser value than the next.
1. Write a function that tests whether a list is ordered or not
2. Write two functions that insert an element to and remove an element
from an ordered list.
Exercise 6.9
A polynomial can be represented by a functional list of pairs composed
of an exponent and a coefficient, ordered by increasing exponent. Write
a function that computes the sum of two polynomials.
since, in n operations, we can move each card from the pile on the left to the
pile on the right. At the end, the card on top of the pile on the left will be on
the bottom of the pile on the right.
This method is linear because we have two places to store cards: the left
pile and the right pile. To write a function reverse that is linear, we must use
the same idea, by writing a function revappend with two arguments x and y
— analogous to the left hand pile and the right hand pile — that computes the
concatenation of the inverse of x with y
static List revappend (final List x, final List y) {
if (x == null) return y;
return revappend(x.tl,new List(x.hd,y));}
Exercise 6.10
Write a function reverse that modifies its arguments and does not al-
locate memory.
Exercise 6.11 (The Cartesian Product of a Family of Sets)
Consider a list l0 , l1 , l2 , ... such that each li is itself a list. Print
all the lists that can be obtained by taking an element in l0 , an element
in l1 , ...
6.5.1 Stacks
The first element of a stack is called the top and not the head of the stack,
since it can be thought of as a vertical pile, like a stack of plates. The operation
that removes the top element can be thought of as returning the tail of a list,
but the difference is that the statement l2 = l1.tl; does not modify the list
l1, while the statement pop(p);, which removes the top of the stack p, modifies
that stack.
Because this statement modifies its argument, it is necessary to represent a
stack, not as a list, but as a list inside a wrapper
class List {
int hd;
List tl;
class Stack {
List c;
Exercise 6.14
We consider a list of characters in the set ’(’, ’)’, ’[’, ’]’. The set
of well formatted lists is the smallest set such that
– the empty list is well formatted,
– if l is well formatted then so are (l) and [l],
– if l1 and l2 are well formatted then so is l1 l2 .
For example, the list ([[()]()][]) is well formatted, but the list
([[()](]][]) is not.
Write a function that indicates whether or not a list of characters is well
formatted of not.
For once, it is easier to use a stack than recursion.
6.5.2 Queues
Exercise 6.17
To make the operation that adds an element faster, we can also represent
a queue by two lists. The pair composed of the lists l1 , ..., lp and
m1 , ..., mq represents the queue l1 , ..., lp , mq , ..., m1 .
Program the above operations for this data structure.
What is the time complexity for the operation that accesses the first
element in the best case? In the worst case? In the average case?
In a hospital’s emergency room, a first come, first serve policy is not always
optimal. A better solution is to assign each patient a priority level and to serve
the patients with a higher priority first.
The operations on priority queues are
1. Creating an empty queue,
2. Testing to see if the queue is empty or not,
3. Adding an element to a queue,
4. Accessing to the next element with the highest priority,
5. Removing the next element with the highest priority.
Exercise 6.18
Program the operations above by representing a priority queue with a
list and by adding new elements to the head.
What is the time complexity of the operation that adds an element to
the queue? And that of the operation that reads the element with the
highest priority?
And what if we represent a priority queue with a list ordered by decreas-
ing priority?
7
Exceptions
boolean indicating whether or not a key is in the domain of the function and
we always use the domain function before using the assoc function.
Each of these solutions has shortcomings: the first solution uses a value
to symbolise the absence of a value, which is somewhat ambiguous and not
immediately clear. The second and third cause the assoc function to have a
more complex type. And the last solution requires the association list to be
traversed twice.
7.2 Exceptions
An alternative solution is to use a construct called an exception. We could use
an exception in the assoc function as follows
static int assoc (final int x, final List l) throws Exception {
if (l == null) throw new Exception();
if (x == l.key) return l.val;
return assoc(x,l.tl);}
This function is a partial function that returns the value associated with the
key x in the list l when this value exists and that fails otherwise. In this
case, we say that the function raises an exception. The statement to raise an
exception is throw e; where e is a value of type Exception. The simplest
method of creating a value of type Exception is to use the new construct: new
Exception();.
The fact that the function assoc can raise an exception must be declared by
adding throws Exception between the list of arguments and the body of the
function. Care must be taken to not confuse the keyword throw, used to throw
an exception, and the keyword throws, used to indicate that an exception can
be thrown.
The exception case is not very different from the return case. In particular,
if in a sequence {p1 p2 } the execution of p1 raises an exception, then the
statement p2 is not executed and the result of the execution of {p1 p2 } is this
same exception.
Exercise 7.1
Extend the definition of the Σ function to take exceptions into consid-
eration.
7.7 Caml
In Caml, the statement throw e is written raise e. It is not necessary to
declare that a function can raise an exception. We declare a new exception with
the statement exception C. We catch an exception with the construct try p
with _ -> q.
Thus, we can start by declaring an exception
exception Not_in_the_list
and define the function assoc and use it in the main program
let rec assoc x l = if l = []
then raise Not_in_the_list
else if fst(List.hd l) = x
then snd(List.hd l)
else assoc x (List.tl l)
in try print_int(assoc 5 [(3,4)])
with _ -> print_string("Not in the list")
There are no exceptions in C, but some constructs like long jumps have
some similarities.
8
Objects
The programs we have seen so far have been composed of type declarations,
global variable declarations, function definitions, and the main program — the
main function.
As we have seen, the use of functions allows for a better layout of programs,
since a main program of a few thousand lines would be very difficult to read and
understand. However, the use of functions isn’t sufficient to create extremely
long programs that also become difficult to read and understand when they
are composed of dozens of functions. Other constructs, such as modules and
objects, allow programs to be even better structured.
8.1 Classes
8.1.1 Functions as Part of a Type
So, instead of defining the stack type, plus its functions, plus the main
program
class Stack {
List c;
Stack ...
}
class Prog {
List c;
Stack ...
class Prog {
To extend the Σ function, it is necessary that, in the list of classes, each class
is associated with a triplet composed of a list of fields, a list of constructors,
and a list of methods.
Exercise 8.1
Define the Σ function to account for the inclusion of method calls. As-
sume, for this exercise, that methods cannot access global variables.
body of the function we use a special variable called this. Then, we call this
method not with the statement or the expression T.f(b1 ,...,a,...,bp ) but
with the statement or the expression a.f(b1 ,...,bp ). This type of method is
called a dynamic method.
For example, instead of defining the methods push and pop as follows
static void push (final int a, final Stack l) {
l.c = new List(a,l.c);}
void pop () {
this.c = this.c.tl;}
We call these methods with p.pop(); p.push(5);. We can then execute the
following program
Stack p = Stack.empty ();
p.push(5);
p.push(6);
System.out.println(p.top());
p.pop();
System.out.println(p.top());
that outputs 6 and 5.
In fact, it is possible to be even more concise, since in the body of a dynamic
method of the class T, if c is a field of the class T and there is no local variable
with the same name, the expression c is an abbreviation for this.c. Thus, you
can define the methods push and pop as follows
void push (final int a) {
c = new List(a,c);}
void pop () {
c = c.tl;}
It is important to note that a dynamic method can only be called when the
object with which it is called does not have the value null. Thus, if append is
a dynamic method, we can write l1.append(l2) only when the list l1 is non
empty. To be able to call the function append with an empty list, you must
8.2 Dynamic Methods 131
either use a static method, or use a wrapper type. For instance, the empty
stack is not represented with the value null, but with an object that contains
a field c whose value is null. Because of this, even if the stack p is empty, the
statement p.push(5); is valid and adds the value 5 to the top of the stack p.
A common error is to write a dynamic method
List f () {
if (this == null) ...}
Basically, the boolean this == null will always be equal to false, because
the method f cannot be called when the object is null.
The extension of the Σ function to include dynamic methods isn’t very
difficult, even if care must be taken to avoid confusing the assignment c = 1;
of the variable c with that of the field c of the object this.
Exercise 8.2
Give the definition of the Σ function, for dynamic methods.
y
a
Write a program that draws the Koch Snowflake — see Exercise 3.6 —
with a turtle.
void print () {
System.out.println(mem);}}
the program
M a = new M();
M b = new M();
a.modify(4);
b.modify(5);
a.print();
outputs 5 and not 4. Because the field mem is static, the body mem = x; of
the method modify is an abbreviation of the statement M.mem = x; and not
this.mem = x;.
8.6 Inheritance
class Clock {
int h;
int mn;
Clock () {}
void drawHand
(final int x, final int y, final double l, final int a) {
double b = (a - 15) * 3.1415926 / 30;
int x2 = x + (int) Math.round(l * Math.cos(b));
int y2 = y + (int) Math.round(l * Math.sin(b));
Ppl.drawLine(x,y,x2,y2);}
All the fields and methods of the class Clock are inherited in the new class.
It is also possible to redefine certain methods, like the method draw, which
must be altered to draw the second hand.
All expressions of type ClockWithSeconds are also of type Clock. If h is
an expression of type Clock created with the constructor Clock and k is an
expression of type Clock created with the constructor ClockWithSeconds, the
statements h.draw(40,40,30); and k.draw(40,40,30); draw a clock without
a second hand in the first case, and with a second hand in the second.
136 8. Objects
Inheritance allows you to give these two objects a different version of the draw
method.
To conclude this short introduction of inheritance, which is a subject com-
plex enough to occupy an entire book, we shall see how it can be applied to
disjunctive types.
Exercise 8.5 (Disjunctive types)
Remember than an arithmetic expression is either a constant, a variable,
the sum or two arithmetic expressions, or the product of two arithmetic
expressions.
We define a type Expr with no fields or constructors, but with a method
print, whose body is immaterial since it will never be executed.
class Expr {
8.7 Caml
Objects in Caml are somewhat different than in Java. In Java, records are
special types of objects. In Caml, records and objects are completely different
constructs. Another difference is that, in Caml, an object’s fields can only be
accessed by that object’s methods. To allow other objects to access its fields, you
must write a getter method, such as get_c below. The last difference is that
there are no static methods in Caml.
As in Java, each field can be a constant or mutable, which is specified with
the keyword mutable. We create a new object with the keyword new and we
access its methods using the symbol #.
The class for a stack is defined, for example, as follows
class stack = object
val mutable c = ([]:int list)
method get_c () = c
method testempty () = c = []
method push x = c <- x::c
method pop () = c <- List.tl c
method top () = List.hd c
end
As Caml has no static methods, the function empty that creates an empty
stack cannot be a method of the class stack and is defined outside of the class
let empty () = new stack
Then, we can use these methods and this function in a program
let p = empty () in
(p#push 5;
p#push 6;
print_int (p#top());
print_newline();
p#pop();
138 8. Objects
print_int (p#top());
print_newline())
There are no objects in C. But there are objects in a language that is a
successor to C, called C++.
9
Programming with Trees
9.1 Trees
We have seen that a tree type is a type in which several fields are recursive.
When two fields are recursive, the type is called a binary tree type.
The simplest tree type is a binary tree type without any non-recursive fields.
class Tree {
Tree left;
Tree right;
}
A value of this type is either null or is a reference associated in memory with
a record composed of two values of the same type.
We will limit ourselves, in this chapter, to states in which the same reference
is used only once. This will exclude infinite values
We will also use a more convenient notation for trees. Each cell will be rep-
resented by a circle. Instead of arrows, we will use line segments. The state
represented in the first figure of this chapter can then be represented as
9.1 Trees 141
A tree is a value of type Tree, either the value null called the empty tree
or a cell.
Let r be a tree that is a cell. The nodes of the tree r are the cells accessible
from r, that is, the elements of the smallest set that contains r and that, if it
contains a cell c, also contains the cell c.left, if this value is a cell and not
the value null, and the cell c.right, if this value is a cell and not the value
null. For example, the value of the variable a above is a tree that contains four
nodes.
By convention, the set of nodes of the empty tree is the empty set.
The size of a tree is its number of nodes.
A node d is the left child of a node c if d = c.left, is the right child of a
node c if d = c.right. The node c is the parent of the node d if d is a child of
c. A node is a leaf node if it has no children. The cell r, seen as a node of the
tree r, is called the root of the tree r. But, formally, a tree and its root are the
same cell.
If c is a node of a tree a, then the trees c.left and c.right are called
the left and right sub-trees of c. These trees can themselves be empty or not.
Formally, the left child and the left sub-tree of a node, if it is not empty, are
the same cell.
A branch of a tree is a sequence of nodes of a such that each element of the
sequence is a child of the node that precedes it. The height of a tree a is the
length of the longest branch of the tree, minus 1. For example, the length of
the longest branch of the tree
142 9. Programming with Trees
is 3 and the height of this tree is then 2. The height of a tree of a single node
is 0. By convention, the height of the empty tree is -1.
All of these definitions generalize immediately to trees with more fields,
recursive or not.
Exercise 9.1
Write a function that computes the number of leaves of a binary tree.
Write a function that computes the number of nodes of a binary tree.
Write a function that computes the number of internal nodes of a binary
tree.
A second tree type is obtained by adding, to the above type, a non recursive
field, for example of type int
class Tree {
int val;
Tree left;
Tree right;}
In this case, the field val of a node c is called the contents of node c.
1 4
5 9 2 6
8 7
When using depth first traversal, you start at the root, descend the length of
the leftmost branch until arriving at a leaf, then climb this branch, descending
as soon as possible down another branch.
1 4
5 9 2 6
8 7
There are several different types of depth first tree traversal. If you visit a node
before descending its left sub-tree, it is known as a preorder traversal. If you
visit a node after returning from the left sub-tree but before descending the
right sub-tree, it is known as an inorder traversal. If you visit a node after
visiting both the left and right sub-trees, it is known as postorder.
In the above example, a preorder traversal visits the nodes in the order 3
1 5 9 8 4 2 6 7, an inorder traversal visits the nodes in the order 5 1 8 9
3 2 4 6 7, and a postorder traversal visits the nodes in the order 5 8 9 1 2
7 6 4 3.
A depth first traversal can be programmed using a while loop and a list
that contains the sub-trees waiting to be traversed. We start by adding a single
element to the list: the tree a. Then, as long as the list is not empty, we
examine its first element. If this tree is empty, we remove it from the list. If
it is a singleton tree, we visit its only node. Otherwise, we decompose the tree
144 9. Programming with Trees
into three smaller trees: its left sub-tree, its right sub-tree, and the singleton
tree formed by its root, and we add these three trees to the list. Since these
three trees must be traversed before the rest in the list, this list should be a
stack.
When performing an inorder traversal, we traverse these three trees in the
order: left, root, right, and can be added to the stack in reverse order.
Stack p = Stack.empty ();
p.push(a);
while (!(p.testempty())) {
Tree b = p.top ();
p.pop();
if (b != null) {
if (singleton(b))
System.out.print(b.val + " ");
else {
p.push(b.right);
p.push(new Tree(b.val,null,null));
p.push(b.left);}}}
By changing the order in which you add the three sub-trees to the stack,
you can program a preorder or postorder traversal.
You can avoid using a stack by programming the traversal recursively
static void traverse (Tree a) {
if (a != null) {
traverse(a.left);
System.out.print(a.val + " ");
traverse(a.right);}}
Exercise 9.2
Write a function that performs a preorder traversal and one that per-
forms a postorder traversal.
Exercise 9.3
What is outputted during a preorder, inorder, and postorder traversal
of the following tree?
5 6
9.2 Traversing a Tree 145
+ *
5 * + 7
6 7 5 6
A breadth first traversal consists of visiting the root, then all of the children of
the root, from left to right, then all of the grandchildren of the root, ...
1 4
5 9 2 6
8 7
This can be programmed like the depth first traversal using a while loop
and a list of sub-trees waiting to be traversed. When you remove an element
from the list, you visit its root and add its left and right sub-trees to the list.
Unlike depth first traversal, these sub-trees can only be visited after visiting all
of the trees already in the list. The stack is therefore replaced with a queue.
Queue p = Queue.empty ();
p.add(a);
while (!(p.testempty())) {
146 9. Programming with Trees
In Chapter 6, we have seen that a finite set can be represented with a list and
that with this representation, testing to see if an element belongs to this set or
not requires a linear average time with respect to the number of elements in
the set.
Now imagine we wanted to find a word in the dictionary by searching
through each and every entry, from cover to cover. We already know a more
efficient method for looking up words in the dictionary: you open the dictionary
roughly in the middle, compare your word with the median entry, which elimi-
nates half of the dictionary. Recursively apply the same procedure to remaining
half, and you will eventually find your word.
This method of binary search requires logarithmic time with respect to
the size of the dictionary. However, this method depends on the fact that the
dictionary is ordered by word, and that words can be accessed randomly with
constant time, which is the case with a book, but would not be true for a
papyrus scroll.
Representing a set by a list does not allow for a binary search, because even
if the list is ordered, accessing the median element of the list requires linear
time, and not constant time, with respect to the size of the list.
In contrast, a binary search is possible if you replace the list with an array.
But this solution has two problems. The size of an array must be known in
advance, and it is not possible to change its size after it has been created. Also,
adding a new element to an array requires linear time with respect to the size
of the array, since it becomes necessary to shift all of the elements larger than
the one being inserted.
9.3 Search Trees 147
9 5
5 19 1 12
1 12 24 9 24
19
Similarly, inserting a new element in the tree requires a time linear in the
height of the tree. We choose a method that modifies its argument and not
a method that copies it. This method returns a tree that is the result of this
insertion. Note that when you execute insert(a);, the value of a after the
method has been executed is the result of this insertion, except in the case
where a is the empty tree.
static Tree insert (final int x, final Tree a) {
if (a == null) return new Tree(x,null,null);
if (x < a.val) {a.left = insert(x,a.left); return a;}
if (x > a.val) {a.right = insert(x,a.right); return a;}
return a;}
The removal of an element of a tree also requires a time linear in the height
of the tree, but the method of doing so is somewhat more difficult than searching
and insertion. Once the node to remove has been found, there are three possible
cases. If this node is a leaf, it can simply be removed. If this node has only one
child, it can be deleted, and its child connected directly to its parent. If this
node has two children, then you must find the largest element of its left sub-
tree, replace the removed node by this element and then recursively remove
this element from the left sub-tree.
Note that the removal of these elements in the left sub-tree can be done in
a single step, since the largest element in a tree cannot have a right child.
12
19
24
y x
x y
C A
A B B C
and a left rotation is the inverse of a right rotation. Such rotations transform
a search tree into another search tree.
Exercise 9.5
We consider a tree such that the two sub-trees of a root are AVL but
such that the difference of the height of these two sub-trees are equal to
2.
Show that you can transform this tree into an AVL tree by applying
rotations to the root and its children.
We distinguish several cases based on the height of the trees whose roots
are the children and grandchildren of the root of the tree to balance.
Exercise 9.6
Write in Java functions for insertion and removal of elements from AVL
trees. How can you avoid having to compute the height of a tree — which
requires a linear time and not a logarithmic time with respect to its size?
9.3.3 Dictionaries
Remember that a priority queue is a set in which each element is given a priority.
Operations on such a set are searching for the element with the highest priority,
insertion of an element, and removal of an element with the highest priority.
In Chapter 6, we have seen that we can represent a priority queue with a list,
but that searching for an element and removing an element with the highest
priority occurs in linear time, unless we decide to order the elements of this list
by priority, in which case insertion becomes linear.
We can render searching, insertion, and removal of an element from a prior-
ity list logarithmic by using a tree, for example a balanced search tree. However,
there is a simpler solution. A binary tree is said to be partially ordered if the
contents of a child of a node are always smaller than the contents of this node.
For example, the set {1, 5, 9, 12, 19, 24} can be represented by the fol-
lowing partially ordered trees
24 24
9 1 19
19
12 1 9 12
5
Searching for the maximum element in a partially ordered tree is easy, since
this element must be the contents of the root of the tree. The time required to
find it is independent of the size of the tree.
Removal of the maximum element is not difficult either, and illustrates
the mechanism of promotion in a hierarchical structure: the boss dies, the best
deputy of the boss becomes boss, the best deputy of the deputy becomes deputy,
... The time required to remove an element is linear in the height of the tree.
Insertion is done in a similar fashion, by adding the new element anywhere
it will fit, and to permute it with its parent if it is larger, and with its new
9.4 Priority Queues 153
parent, ... The time required to insert an element is linear in the height of the
tree.
Once again, to ensure that the operations of insertion and removal are logarith-
mic, you must balance the tree. Here, in contrast to the case of search trees, it
is possible to ensure that the tree is always of minimum size, and even that it
is packed, that is to say that the nodes of the bottom level are all located as
far left as possible. For example, the first of the trees below is packed, but not
the second, although both are of minimum height.
1 1
2 3 2 3
4 5 6 4 6 7
154 9. Programming with Trees
A packed, partially ordered tree is called a heap. This notion of a heap has
nothing to do with the notion of a set of cells, as seen before.
24 1
19 9 3
2
1 12 5
4 5 6
Like all partially ordered trees, we can find the maximum element in a heap
in a time independent of the size of the heap, since the maximum element is
contained in the root.
Adding an element occurs in logarithmic time. We start by adding the new
element in the first empty space that keeps the tree packed, and we permute
this element with its parent while it is larger than its parent, which reorders
the tree.
To remove the maximum element, we replace the root by the element with
the largest number — the lowest right element in the tree — so that the tree
remains packed, and we permute it with the largest of its children while it is
smaller than one of its children.
Exercise 9.8
Write in Java functions for searching, insertion, and removal of an ele-
ment in a heap.
Since the numbers of a packed tree are contiguous, we can represent such
a tree with an array by putting the contents of the node with number i + 1
in compartment i of the array. The children of the node of compartment i
— node of number i + 1 — are in compartments 2 i + 1 and 2 i + 2 and
the parent of the node of compartment i is in compartment (i - 1) / 2.
If compartments 0 ... i - 1 of an array represent a heap, the insertion
in the heap of the element located in compartment i of the array is written as
follows
static void insert (final int [] t, final int i) {
int j = i;
while (j >= 1 && t[(j-1)/2] <= t[j]) {
int z = t[j]; t[j] = t[(j-1)/2];t[(j-1)/2] = z;j = (j-1)/2;}}
9.4 Priority Queues 155
whose first 128 lines are white, the array would start with 32768 zeros.
A more efficient alternative, is to use a quaternary tree. We represent a
monochrome image by a singleton tree whose single node contains the
156 9. Programming with Trees
0 0
and expressing that the top half of the image is entirely white requires
only two nodes of the tree.
Write a function that transforms an array of 0 and 1 into a tree and
vice-versa.
Index
core
allocation, 61 – functional, 55
argument – imperative, 1
– formal, 21
– real, 21 definition
argument passing – function, 20
– by reference (or by variable), 39 – recursive, 48
– by value, 39 do, 16
array, 79 double, 3
assignment, 1 dynamic data, 85
binary search, 146
boolean, 3 environment, 9
branch of a tree, 141 – global, 29
byte, 3 equality
– physical, 68
catch, 122 – structural, 68
cell, 61 error message, 124
– free, 98 Exception, 122
char, 3 exception, 122
child exception, 125
– left, 141 expression, 1
– right, 141 – arithmetical, 90
class, 60 extends, 135
– static, 133
class, 26, 60 false, 3
complexity, 113 fifo, 118
– average, 113 float, 3
– worst case, 113 for, 16
computable, 17 function, 20
concatenation, 105 function body, 20
const, 5 function call, 20
constant, 5
constructor, 64, 93 garbage collection, 98
contents of a node of a tree, 142 giveup, 14
157
158 Index
– scalar, 3 variable, 1, 8
– tree, 89 – constant, 5
– wrapper, 68 – global, 25
– mutable, 5
unit, 24 variable declaration, 3
update, 8 visit, 142
void, 24
Val, 8
value, 2, 8, 104
Var, 8 while, 6
var, 39 with, 125