Reasoned Programming
Reasoned Programming
Foreword xi
Preface xiii
1 Introduction 1
1.1 How do you know a program does what you want it to? 1
1.2 Why bother? 1
1.3 What did you want your program to do? 2
1.4 Local versus global behaviour 3
1.5 Reasoned programs 4
1.6 Reasoned programming 5
1.7 Modules 6
1.8 Programming in the large 7
1.9 Logical notation 8
1.10 The need for formality 10
1.11 Can programs be proved correct? 11
1.12 Summary 12
I Programming 13
2 Functions and expressions 15
2.1 Functions 15
2.2 Describing functions 16
2.3 Some properties of functions 21
2.4 Using a functional language evaluator 22
2.5 Evaluation of expressions 22
2.6 Notations for functions 24
v
vi Contents
2.7 Meaning of expressions 25
2.8 Summary 26
2.9 Exercises 26
3 Specications 27
3.1 Specication as contract 27
3.2 Formalizing specications 28
3.3 Defensive specications | what happens if the input is bad? 29
3.4 How to use specications: fourthroot 30
3.5 Proof that fourthroot satises its specication 31
3.6 A little unpleasantness: error tolerances 34
3.7 Other changes to the contract 35
3.8 A careless slip: positive square roots 36
3.9 Another example, min 37
3.10 Summary 38
3.11 Exercises 38
4 Functional programming in Miranda 40
4.1 Data types | bool, num and char 40
4.2 Built-in functions over basic types 41
4.3 User-dened functions 44
4.4 More constructions 47
4.5 Summary 51
4.6 Exercises 52
5 Recursion and induction 53
5.1 Recursion 53
5.2 Evaluation strategy of Miranda 54
5.3 Euclid's algorithm 55
5.4 Recursion variants 57
5.5 Mathematical induction 60
5.6 Double induction | Euclid's algorithm without division 63
5.7 Summary 65
5.8 Exercises 65
6 Lists 68
6.1 Introduction 68
6.2 The list aggregate type 68
6.3 Recursive functions over lists 72
6.4 Trapping errors 75
6.5 An example | insertion sort 76
6.6 Another example | sorted merge 81
6.7 List induction 82
6.8 Summary 86
Contents vii
6.9 Exercises 87
7 Types 91
7.1 Tuples 91
7.2 More on pattern matching 93
7.3 Currying 94
7.4 Types 96
7.5 Enumerated types 100
7.6 User-dened constructors 100
7.7 Recursively dened types 102
7.8 Structural induction 106
7.9 Summary 111
7.10 Exercises 112
8 Higher-order functions 117
8.1 Higher-order programming 117
8.2 The higher-order function map 117
8.3 The higher-order function fold 120
8.4 Applications 121
8.5 Implementing fold | foldr 122
8.6 Summary 123
8.7 Exercises 124
9 Specication for Modula-2 programs 129
9.1 Writing specications for Modula-2 procedures 129
9.2 Mid-conditions 131
9.3 Calling procedures 133
9.4 Recursion 135
9.5 Examples 136
9.6 Calling procedures in general 138
9.7 Keeping the reasoning simple 139
9.8 Summary 139
9.9 Exercises 140
10 Loops 141
10.1 The coee tin game 141
10.2 Mid-conditions in loops 144
10.3 Termination 145
10.4 An example 145
10.5 Loop invariants as a programming technique 148
10.6 FOR loops 149
10.7 Summary 151
10.8 Exercises 151
viii Contents
11 Binary chop 154
11.1 A telephone directory 154
11.2 Specication 155
11.3 The algorithm 156
11.4 The program 158
11.5 Some detailed checks 159
11.6 Checking for the presence of an element 160
11.7 Summary 161
11.8 Exercises 162
12 Quick sort 164
12.1 Quick sort 164
12.2 Quick sort | functional version 164
12.3 Arrays as lists 166
12.4 Quick sort in Modula-2 167
12.5 Dutch national ag 169
12.6 Partitions by the Dutch national ag algorithm 172
12.7 Summary 174
12.8 Exercises 174
13 Warshall's algorithm 176
13.1 Transitive closure 176
13.2 First algorithm 178
13.3 Warshall's algorithm 181
13.4 Summary 184
13.5 Exercises 184
14 Tail recursion 186
14.1 Tail recursion 186
14.2 Example: gcd 188
14.3 General scheme 189
14.4 Example: factorial 190
14.5 Summary 192
14.6 Exercises 193
II Logic 195
15 An introduction to logic 197
15.1 Logic 197
15.2 The propositional language 198
15.3 Meanings of the connectives 200
15.4 The quantier language 203
15.5 Translation from English 205
Contents ix
15.6 Introducing equivalence 208
15.7 Some useful predicate equivalences 209
15.8 Summary 210
15.9 Exercises 211
16 Natural deduction 214
16.1 Arguments 214
16.2 The natural deduction rules 215
16.3 Examples 230
16.4 Summary 235
16.5 Exercises 235
17 Natural deduction for predicate logic 237
17.1 8-elimination (8E ) and 9-introduction (9I ) rules 237
17.2 8-introduction (8I ) and 9-elimination (9E ) rules 242
17.3 Equality 247
17.4 Substitution of equality 249
17.5 Summary 255
17.6 Exercises 256
18 Models 260
18.1 Validity of arguments 260
18.2 Disproving arguments 264
18.3 Intended structures 267
18.4 Equivalences 268
18.5 Soundness and completeness of natural deduction 271
18.6 Proof of the soundness of natural deduction 273
18.7 Proof of the completeness of natural deduction 275
18.8 Summary 279
18.9 Exercises 280
A Well-founded induction 282
A.1 Exercises 287
B Summary of equivalences 288
C Summary of natural deduction rules 289
Further reading 293
Foreword
How do you describe what a computer program does without getting bogged
down in how it does it? If the program hasn't been written yet, we can ask
the same question using a dierent tense and slightly dierent wording: How
do you specify what a program should do without determining exactly how it
should do it? Then we can add the question: When the program is written,
how do you judge that it satises its specication?
In civil engineering, one can ask a very similar pair of questions: How can
you specify what a bridge should do without determining its design? And,
when it has been designed, how can you judge whether it does indeed do
what it should?
This book is about these questions for software engineering, and its answers
can usefully be compared with what happens in civil engineering. First, a
specication is a dierent kind of thing from a design the specication of a
bridge may talk about load-bearing capacity, deection under high winds and
resistance of piers to water erosion, while the design talks about quite dierent
things such as structural components and their assembly. For software, too,
specications talk about external matters and programs talk about internal
matters.
The second of the two questions is about judging that one thing satises
another. The main message of the book, and a vitally important one, is that
judgement relies upon understanding . This is obviously true in the case of the
bridge the judgement that the bridge can bear the specied load rests on
structural properties of components, enshrined in engineering principles, which
in turn rest upon the science of materials. Thus the judgement rests upon a
tower of understanding.
This tower is well-established for the older engineering disciplines for
software engineering, it is still being built. (We may call it `software science'.)
The authors have undertaken to tell students in their rst or second year
about the tower as it now stands, rather than dictate principles to them. This
xi
xii Foreword
is refreshing in software engineering there has been a tendency to substitute
formality for understanding. Since a program is written in a very formal
language, and the specication is also often written in formal logical terms, it
is natural to emphasize formality in making the judgement that one satises
the other. But in teaching it is stultifying to formalize before understanding,
and software science is no exception | even if the industrial signicance of a
formal verication is increasingly being recognized.
This book is therefore very approachable. It makes the interplay between
specication and programming into a human and exible one, albeit guided
by rigour. After a gentle introduction, it treats three or four good-sized
examples, big enough to give condence that the approach will scale up to
industrial software at the same time, there is a spirit of scientic enquiry.
The authors have made the book self-contained by including an introduction
to logic written in the same spirit. They have tempered their care for
accuracy with a light style of writing and an enthusiasm which I believe will
endear the book to students.
Robin Milner
University of Edinburgh
January 1994
Preface
Can we ever be sure that our computer programs will work reliably? One
approach to this problem is to attempt a mathematical proof of reliability,
and this has led to the idea of Formal Methods: if you have a formal, logical
specication of the properties meant by `working reliably', then perhaps you
can give a formal mathematical proof that the program (presented as a formal
text) satises them.
Of course, this is by no means trivial. Before we can even get started
on a formal proof we must turn the informal ideas intended by `working
reliably' into a formal specication, and we also need a formal account of
what it means to say that a program satises a specication (this amounts
to a semantics of the programming language, an account of the meaning of
programs). None the less, Formal Methods are now routinely practised by a
number of software producers.
However, a tremendous overhead derives from the stress on formality,
that is to say, working by the manipulation of symbolic forms. A formal
mathematical proof is a very dierent beast from the kind of proof that you
will see in mathematical text books. It includes the minutest possible detail,
both in proof steps and in background assumptions, and is not for human
consumption | sophisticated software support tools are needed to handle it.
For this reason, Formal Methods are often considered justiable only in `safety
critical' systems, for which reliability is an overriding priority.
The aim of this book is to present informal formal methods, showing
the benets of the approach even without strict formality: although we use
logic as a notation for the specications, we rely on informal semantics
| a programmer's ordinary intuitions about what small, linear stretches of
code actually do | and we use proofs to the level of rigour of ordinary
mathematics.
This can, of course, serve as a rst introduction to strict Formal Methods,
but it should really be seen much more broadly. The benets of Formal
xiii
xiv Preface
Methods do not accrue just from the formality. The very eort of writing a
specication prior to the coding focuses attention on what the user wants to
get out of the program, as opposed to what the computer has to do, and the
satisfaction proof, even if informal, expresses our idea of how the algorithm
works. This does not require support tools, and the method | which amounts
really to methodical commenting | is practicable in all programming tasks.
Moreover, the logic plays a key role in modularization, because it bundles
the code up into small, self-contained chunks, each with its specic task
dened by the logic.
Although most of the techniques presented are not new (and can be found,
for instance, in the classic texts of Gries and Reynolds), we believe that many
aspects of our approach are of some novelty. In particular
Functional programming: Functional programming is presented as a
programming language in its own right (and we include a description
of the main features of Miranda) but we also use it as a reasoning
tool in imperative programming. This is useful because the language
of functional programming is very often much clearer and more concise
than that of imperative programming (the reason being that functional
programs contain less detail about how to solve a problem than do
imperative programs).
Procedures: It is dicult to give a semantics that covers procedures, and
many treatments (though not Reynolds') ignore them. This is reinforced
by the standard list of ingredients of structured programming (sequence,
decision and iteration), which are indeed all that is structurally necessary
but in fact procedures are the single most eective structure in making
large programs tractable to human minds and this is because they are the
basic unit of interface between specication and code | both inwards,
between the specication and implementing code, and outwards, between
the specication and calling code. The role of the logical specication
in promoting modularity is crucial, and we have paid unusual attention
to showing not only how specications may be satised but also to how
they may be used.
Loop invariants: We have tried hard to show loop invariants as an expression
of initial intuitions about the computation, rather than as either a post
hoc justication or as something that appears by magic by playing with
the post-condition. Often they arise naturally out of diagrams nearly
always they can function as statements of intent for what sections of
the code are to do. We have never shirked the duty of providing them.
Experience even with machine code shows that the destinations of jumps
are critical places at which comments are vital, and this covers the case
of loop invariants.
Real programming languages: We have done our best to address real
programming problems by facing up to the complexities of real imperative
Preface xv
languages, saying what can be said rather than restricting ourselves to
articial simplicities. Thus, while pointing out that reasoning is simpler
if features such as side-eects are avoided, we have tried to show how
the more complicated features might be attacked, at least informally.
The book is divided into two complementary parts, the rst on Programming
and the second on Logic. Though they are both about logical reasoning, the
rst half concerns the ideas about programs that the reasoning is intended
to capture, while the second half is more about the formal machinery.
The distinction is somewhat analogous to that often seen in books about
programming languages: a rst part is an introduction to programming using
the language, and a second part is a formal report on it.
To read our book from scratch, one would most likely read the two parts in
parallel, and this is in fact how we teach the material for our main computer
science course at Imperial. However, the division into two reasonably disjoint
parts means that people who already have some background in logic can see
the programming story told without interruption.
The approach to the logic section has been strongly inuenced by our
experience in teaching the subject as part of a computer science course. We
put great stress right from the start on the use of the full predicate logic as
a means of expression, and our formal treatment of logical proof is based on
natural deduction because it is natural | its formal structure does reect
the way informal mathematical reasoning is carried out (in the rst part, for
instance). We have taken the opportunity to use the two parts to enrich each
other, so, for instance, some of the proofs about programs in the rst part
are presented as illustrations of the box proof techniques of the second part,
and many of the logic examples in the second part are programs.
Part I Programming Part II Logic
1 Introduction
2 Functions and expressions 15 An introduction to logic
3 Specications
4 Functional programming in Miranda
5 Recursion and induction
6 Lists 16 Natural deduction
7 Types
8 Higher-order functions
17 Natural deduction for predicate logic
18 Models
9 Specication for Modula-2 programs
10 Loops
11 Binary chop
12 Quick sort
13 Warshall's algorithm
14 Tail recursion
xvi Preface
The preceding contents list shows the order in which we cover the material
in the rst year of our undergraduate computer science course. The gap in
Part I, between Chapters 8 and 9, is where we teach Modula-2 as a language.
Students who have already been taught an imperative programming language
would be able to carry straight on from Chapter 8 to Chapter 9.
There are other courses that could be based on this book. Either part makes
a course without the other, and indeed in a dierent class we successfully
teach the Part II material separately with Part I following. For the more
mathematically minded who nd imperative program reasoning inelegant,
Chapters 9 through 14 could be omitted and this would then enable the
material to be taught in a single semester course.
Acknowledgements
We would like our rst acknowledgement to be to David Turner and Research
Associates for the elegant Miranda language and the robust Miranda system.
Much of the written material has been handed out as course notes over the
years and we thank those students and academic sta who attempted the
exercises and read, puzzled over and commented on one section or another.
We would also like to thank Paul Taylor for his box proof macros Lee
McLoughlin for helping with the diagrams Kevin Twidle for keeping our
production system healthy Mark Ryan for helping to turn Word les into
LATEX Peter Cutler, Iain Stewart and Ian Moor for designing and testing
many of the programs special thanks must go to Roger Bailey whose Hope
course turned into our Miranda lectures.
Lastly, we would like to credit those who have inspired us. Courses evolve
rather than emerge complete. Reasoned Programming could not have existed
in its current form without the ideas of Samson Abramsky and Dov Gabbay,
for which we are most grateful.
Krysia Broda,
Susan Eisenbach
Hessam Khoshnevisan,
Steve Vickers
Imperial College,
January 1994
Chapter 1
Introduction
1.1 How do you know a program does what you want it to?
You write a computer program in order to get the computer to do something
for you, so it is not dicult to understand that when you have written
a program you want to be reasonably condent that it does what you
intended. A common approach is simply to run it and see. If it does
something unexpected, then you can try to correct the errors. (It is common
to call these `bugs', as though the program had blamelessly caught some
disabling infection. Let us instead be ruthlessly frank and call them `errors',
or `mistakes'.) Unfortunately, as the computer scientist Edgser Dijkstra has
pointed out, testing can only establish the presence of errors, not the absence,
and it is common to regard programs as hopelessly error-prone. It would
be easy to say that the answer is simple: Don't write any errors! Get the
program right rst time! Novice programmers quickly see the fatuity of this,
but then fall into the opposite trap of not taking care to keep errors out.
In practical programming there are various techniques designed to combat
errors. Some help you to write error-free programs in the rst place, while
others aim to catch errors early when they are easier to correct. This book
explains one particular and fundamental idea: the better you understand what
it is that the program is supposed to do, the easier it is to write it correctly.
1
2 Introduction
and you the purchaser are assuming the entire risk as to its quality and
performance.
Fortunately, the programmers engaged to write the software did not treat
this legal disclaimer as the denitive statement of what the program was
supposed to do. They worked hard and conscientiously to produce a
well-thought-out and useful product of which they could be proud. None
the less, the potential is for even the tiniest of software errors to produce
catastrophic failures. This worries the legal department, and, for the sake at
least of legal consequences, they do their best to dissociate the company from
uses to which the software is put in the real world.
There are other contexts where litigation is not even a theoretical factor.
For instance, if you work in a software house, your colleagues may need to use
your software, and they will want to be condent that it works. If something
goes wrong, blanket disclaimers are quite beside the point. The management
will want to know what went wrong and what you are doing about it.
More subtly, you are often your own customer when you write dierent
parts of the program at dierent times or reuse parts of other programs. This
is because, by the time you come to reuse the code, it is easy to forget what
it did.
All in all, therefore, we see that the quality you are trying to achieve in
your software, and the responsibility for avoiding errors, goes beyond what
can be dened by legal or contractual obligations.
The local behaviour of this is that it does four walks with right angles
in between a global property is that it ends up at the starting position.
The program does not explicitly describe this global property we need some
geometry to deduce it. This is not trivial because, with the wrong geometry,
the global property can fail! Therefore, the geometric reasoning must be deep
enough to resolve this.
Consider this program:
GO 10000 km TURN LEFT 90 DEGREES
GO 10000 km TURN LEFT 90 DEGREES
GO 10000 km TURN LEFT 90 DEGREES
GO 10000 km TURN LEFT 90 DEGREES
4 Introduction
North Pole
Paris
Libreville Nias
Figure 1.1
You would not end up where you started, if you started at the North Pole
and walked round the Earth (Figure 1.1).
The metre was originally dened as one ten millionth part of the Earth's
circumference from the North Pole to the Equator via Paris, so the Walkies
trip here goes from the North Pole to Libreville (via Paris), then near to a
little island called Nias, then back to the North Pole, and then on to Libreville
again. You don't get back to where you started. Thus the global properties
of a program can depend very much on hidden geometrical assumptions: is
our world at or round? They are not explicit in the program.
Walkies is not a typical programming language, and properties of programs
do not typically depend on geometry, although like Walkies, their behaviour
may depend on environmental factors. But it is nevertheless true that program
code usually describes just the individual execution steps and how they are
strung together, not their overall eect.
thinking typing
precise requirements
global properties
what the algorithm does
comments or specication
expressed in logic
Figure 1.2
1.7 Modules
This distinction that we have made between specication and code,
corresponding to users and computer, also makes sense inside a program. It
is common to nd that part of the program, with a well-dened task, can
be made fairly self-contained and it is then called | in various contexts |
a subprogram, or subroutine, or procedure or function, or, for larger, more
structured pieces of program, a module. The idea is that the overall program
is a composite thing, made up using components: so it takes on the role of
user.
A module can be specied, and this describes how its environment, the rest
of the program, can call on it and what that achieves. The specication
Programming in the large 7
describes all that the rest of the program needs to know about the module.
The implementation of the module, the code that it contains, its inner
workings, is hidden and can be ignored by the rest of the program.
Modularization is crucial when you want to write a large program because
it divides the overall coding problem into independent subproblems. Once you
have specied a module, you can code up the inside while forgetting the
outside, and vice versa. The specications of the modules also act as bulkheads,
like the partitions in the hold of a ship that stop water from a hole spreading
everywhere and sinking the ship. The specications compartmentalize the
program so that if an error is discovered in one module you can easily check
whether or not correcting it has any consequences for the others. This helps
to avoid the `Hydra' problem, in which correcting one error introduces ten
new ones.
1.12 Summary
The code is directed towards the computer, giving it its instructions.
The specication is directed towards the users, describing what they will
get out of the program. It is concerned with quality (tness for purpose).
By reasoning that the code satises the specication, you link them
together into a reasoned program.
By putting the specication rst, as objectives to be achieved by the
code, you engage in reasoned programming. Coding is then concerned
with correctness (conformance with specication).
This separation also underlies modularization. The specication of a
module or subroutine is its interface with the rest of the program, the
coding is its (hidden) internal workings.
This book is about programming in the small. It makes the simplifying
assumption that specications can be got right rst time.
In practice, specications can be faulty | so that correctness does not
necessarily produce quality. Be on your guard against this.
The earlier faults are corrected, the better and cheaper.
There are numerous practices aimed at obtaining good specications
early rather than late, for instance talking to the customer, thinking
hard about the design and prototyping, but this book is not concerned
with these.
To match the formality of the programming language, we use formal
logical notation for specications. It is also possible to use formal
semantics to link the two, but we will not do this here.
Part I
Programming
Chapter 2
2.1 Functions
From the specication point of view a function is a black box which converts
input to output. `Black box' means you cannot see | or are not interested in
| its internal workings, the implementing code. Mathematically speaking, the
input and output represent the argument to the function and the computed
result (Figure 2.1).
`Give me input'
function
`I'll give you output'
Figure 2.1
In Figure 2.2 the function add1 simply produces a result which is one
more than its given argument. The number 16 is called an argument or an
actual parameter and the process of supplying a function with a parameter
is called function application. We say that the function add1 is applied to
16. Similarly, the function capital takes arguments which are countries and
returns the capital city corresponding to the given country.
15
16 Functions and expressions
16 Denmark
17 Copenhagen
result type:number result type:Cities
Figure 2.2
From mathematics we are all familiar with functions which take more than
one argument. For example, functions + and * require two numbers as
arguments. Figure 2.3 gives some examples of applications of multi-argument
functions.
3 8 3 4 4 3 3 6 8
3 81 64 3
Figure 2.3
When we rst dene a function we need to pay attention both to the
way it works as a rule for calculation (the code) and also to its overall
global, external behaviour (the specication). But, when a function comes to
be used, only its external behaviour is signicant and the local rule used in
calculations and evaluations becomes invisible (a black box). For example,
whenever double is used the same external behaviour will result whether
double n is dened as 2*n or as n+n.
double
16
Figure 2.4
we can draw a diagram | a mapping diagram showing for each input element
its corresponding result (Figure 2.5). However, often there will be many, even
innitely many, individual elements to consider and such methods will clearly
be inconvenient.
argument add1 0 = 1 add1 x = x+1
0 argument add1 1 = 2 ageneral
few equations
.
1 1 .
2 2 an equation
for each possible
argument
.
.
3
.
.
natural
numbers positive
numbers
Figure 2.5
An alternative method is to describe the function using a few general
equations (Figure 2.5). Here we can make use of formal parameters, which are
names that we give to represent any argument to which the function will be
applied. For example, the formal parameter x in the denition of add1 stands
for any number. The right hand side of the rule (or equation) describes
the result computed in terms of the formal parameter. In the functional
language Miranda, add1 is described in a notation which is very close to the
mathematical notation used above:
add1 :: num -> num
add1 x = x+1
18 Functions and expressions
The rst line declares the function by indicating the function's argument
and result types. The argument type, which precedes the arrow, states the
expected type of the argument to which the function will be applied. The
result type, which follows the arrow, states the type of the value returned by
the function. A function is said to map a value from an argument type to
another value of a result type. The second line is an equation which denes
the function.
Now let us look at some more programs: for example, consider the problems
of nding the area and circumference of a circle given its radius. We need
the constant value , which is built-in to the Miranda evaluator under the
name pi, but note that even if pi were not built-in we could dene our own
constant as shown for mypi:
mypi :: num
mypi = 3.14159
circumference, areaofcircle :: num -> num
areaofcircle radius = pi * radius * radius
circumference r = 2 * pi * r
(This also illustrates how a formal parameter is not restricted to a single
letter such as n.) Similarly, we can dene a function to convert a temperature
given in degrees Fahrenheit to degrees Celsius by:
fahr_to_celsius :: num -> num
fahr_to_celsius temp = (temp - 32) / 1.8
Multi-argument functions can be dened similarly. For example, see the
function below, which, given the base area and the height of a uniform object,
computes the volume of the object:
volume :: num -> num -> num
volume hgt area = hgt * area
The declaration is read as follows: volume is a function which takes two
numbers as arguments and returns a number as its result. The reason for
using -> to separate the two number arguments will become clear later when
we discuss typing in more detail. Each -> marks the type preceding it as an
argument type.
double
48
Figure 2.6
There is no restriction on the number of times this principle may be
employed as long as the result and argument types of the various pairs of
functions match.
If we use functional composition without explicit (that is, actual) arguments
then the combination can be regarded as a new function (a composition of
double and *), which we will call doubleprod (Figure 2.7). This new function
4 6
4 6
*
doubleprod
double
48
48
glass box new black box
you can see how it works inside
Figure 2.7
has the property that, for all numbers x and y,
doubleprod :: num -> num -> num
doubleprod x y = double(x*y)
20 Functions and expressions
As another example consider the following function, which computes the volume
of a cylinder of height h and radius r (Figure 2.8). A cylinder is a particular
kind of `uniform object' whose volume we calculate by multiplying its height,
which is h, by its base area, which we calculate using areaofcircle. Hence,
assuming that volume and areaofcircle compute correctly (conform to their
specications), our function for the volume of a cylinder can be computed by
cylinderV :: num -> num -> num
cylinderV h r = volume h(areaofcircle r)
This is an example of top-down design. Ultimately, we want to implement
h r
h r
areaofcircle
cylinderV
volume
cylinderV h r
cylinderV h r
Figure 2.8
the high-level functions, the ones we really want, by building them up from
the low-level, primitive functions, that are built-in to Miranda. But we can
do this step by step from the top down, for instance by dening cylinderV
using functions volume and areaofcircle that do not need to have been
implemented yet, but do need to have been specied.
It can therefore be seen that black boxes (that is, functions) can be plugged
together to build even bigger black boxes, and so on. The external, black
box view of functions, which allows us to encapsulate the complicated internal
plugging and concentrate on the specication, has an important impact on
the cohesion of large programs. To see an example of this, suppose we had
mistakenly dened
areaofcircle radius = pi*pi*radius
Of course, cylinderV will then give wrong answers. But we are none the less
convinced that the denition of cylinderV is correct, and that is because our
use of areaofcircle in it is based on the specication of areaofcircle, not
on the erroneous denition. (volume asks for the base area, and areaofcircle
is supposed to compute this.)
Some properties of functions 21
There may be lots of parts of a large program, all using areaofcircle
correctly, and all giving wrong answers. As soon as areaofcircle has been
corrected, all these problems will vanish.
On the other hand, someone might have been tempted to correct for the
error by dening
cylinderV h r = volume h ((areaofcircle r)*r/pi)
This is a perfect recipe for writing code that is dicult to understand and
debug: as soon as areaofcircle is corrected, cylinderV goes wrong. The
rule is:
When you use a function, rely on its specication, not its denition.
1 0 42
0 1 undened
Figure 2.9
Similarly, division is a partial function and is said to be undened for cases
where its second argument is zero. A function that is not partial, one for
which the result is always dened (at least for arguments of the right type),
is called total.
22 Functions and expressions
Just as an illustration of some dierent possible behaviours of functions,
here are two more kinds:
1. A function is onto if every value of the result type is a possible result
of the function.
2. A function is one-to-one if two dierent combinations of arguments must
lead to two dierent results.
For instance, double is one-to-one (if x 6= y then double x 6= double y) but
not onto (for example, 3 is not a possible result because the results are all
even). On the other hand, volume is onto (for example, any number z is
a possible result because z = volume z 1) but not one-to-one (for example,
volume 2 3 = 6 = volume 3 2 | dierent argument combinations (2,3) and
(3,2) lead to the same result).
1 2 3
=
0 .
14 will be printed by the evaluator. (At each stage we have underlined the
part that gets reduced next.) Other reduction sequences are possible, though
of course they lead to the same answer in the end. Here is another one:
double (3 + 4)
= (3 + 4) +(3 + 4) by the rule for double
= 7 +(3 + 4) by built-in rules for +
= 7+7 by built-in rules for +
= 14 by built-in rules for +
f x y
Figure 2.11
However, we cannot do without parentheses altogether, for we need them
to package f x y as a single unit (f x y) when it is used within a larger
expression. You can see this in
cylinderV h r = volume h(areaofcircle r)
Precedence
In expressions such as (2+3*4), where there are several inx operators, it
is ambiguous whether the expression is meant to represent ((2+3)*4) or
(2+(3*4)). Such ambiguities are resolved by a set of simple precedence
Meaning of expressions 25
(priority) rules. For example, the above expression really means (2+(3*4))
because, by long-standing convention, multiplication has a higher precedence
relative to addition.
The purpose of precedence rules is to resolve possible ambiguity and to allow
us to use fewer parentheses in expressions. Such rules of precedence will also
be built-in to the evaluator to enable it to recognize the intended ordering.
A hand evaluation example illustrating this is shown in Figure 2.12. Where
necessary, the programmer can use extra parentheses to force a dierent order
of grouping. For instance, 2*(3+4)*5 = 2*7*5 = 70.
2*3 + 4*5
6+20
26
* has higher precedence than -
Figure 2.12 2
3 + 4
5 = 6 + 20 = 26
2.8 Summary
A functional program consists of a collection of function denitions. To
run a program one presents the evaluator with an expression and it will
evaluate it. This expression can contain references to functions dened
in the program, as well as to built-in functions and constant values.
Functions are dened in a notation which is very close to mathematical
notation.
Functional composition (that is, passing the output of one function as an
argument to another function) is used to dene more complex functions
in terms of simpler ones.
Evaluation of an expression is by reduction, meaning simplication. The
expression is repeatedly simplied until it has no more functions left to
be applied.
The meaning of an expression is the value which it represents. This
value cannot be changed by any part of the computation. Evaluating an
expression only alters its form, never its value.
2.9 Exercises
1. Dene a function hypotenuse which, given the lengths of the two shorter
sides of a right-angled triangle, returns the length of the third side.
(There is a built-in function sqrt that calculates square roots.)
2. Write a function addDigit of two arguments which will concatenate a
single-digit integer onto the right-hand end of an arbitrary-sized integer.
For instance, addDigit 123 4 should give 1234. Ignore the possibility of
the number becoming too large (called integer overow).
3. Dene a function celsius to fahr that converts celsius temperatures to
Fahrenheit. Show for any value of temp that the following hold:
celsius to fahr(fahr to celsius temp) = temp
fahr to celsius(celsius to fahr temp) = temp
Chapter 3
Speci cations
pre-condition
Programmer's spring
post-condition
Customer's spring Strengthen
Figure 3.1
optional because the compiler can deduce the types from the rest of the
denition. However, they may not be the intended types so type declarations
should always be included in all programs.
The pre- and post-conditions are written using English and logical notation,
and made into comments (that is, they follow the symbols `||'). Note that
in Miranda any part of your program which starts with ||, together with all
the text to its right (on the same line), is regarded as being a comment and
hence is ignored by the evaluator.
The square root function could be specied by
sqrt :: num -> num
||pre: x >= 0
||post: (sqrt x)^2 = x
sqrt x= Hacker must ll this in
Figure 3.2
proof that, as you read down through it, steadily accumulates more and more
true consequences of the assumptions until it reaches the desired conclusion.
That is how the proof can be read, but we can see already that writing it
does not go straight down from top to bottom | we are going to have an
interplay between working forwards from the assumptions and backwards from
the desired conclusions.
The method is fully investigated in Chapter 16. In this example we give a
rather informal introduction to it.
Here is a typical backward step. To prove the conclusion, we must show
that if someone gives us a number | and we don't care what number it is
32 Specications
| as long as it is non-negative, fourthroot will calculate a fourth root of it.
Once we have the number it is xed, so let us give it a dierent name c to
indicate this. So we are now working in a hypothetical context where
1. we have been given our c
2. c is a number
3. c 0
and given all these assumptions we must prove (fourthroot c)4 = c. Figure 3.3
shows a box drawn around the part of the proof where these temporary
assumptions are in force. For the nal conclusion we have left c behind, so we
Figure 3.3
can come out of the box. What this purely logical, and automatic, analysis
has given us is a context (the box) where we can begin to come to grips
with the programming issues. Since c 0, we can use our original assumption
(that sqrt works) to deduce that sqrt c gives an answer y (with y2 = c),
and either y 0 or y < 0.
We thus | again by automatic logic, but working forwards this time |
have two cases to work with, which again we put in boxes because each case
has a temporary assumption (y 0 for one, y < 0 for the other). In each
case, we must prove (fourthroot c)4 = c, so this equation ends up by being
written down three times. This can be seen in Figure 3.4. The two cases may
then be argued by chains of equations as in the nal box proof, Figure 3.5.
Notice the following features of box proofs:
1. Each box marks the scope, or region of validity, of some names or
assumptions. For instance, within the left-hand innermost box we are
working in a context where
we have a number c
c0
y = sqrt c 0
Proof that fourthroot satises its specication 33
These are not permanent for instance outside the boxes nothing is
known of c, and the right-hand innermost box does not know that y 0.
34 Specications
2. When you read a box proof, you can read it straight down from the
top: each new line is either a temporary hypothesis or is derived from
lines higher up. But when you construct a box proof you work both
forwards, from assumptions, and backwards, from your goal. Hence there
is a denite dierence between proof and proving. (It is very similar to
that between the Reasoned Program and Reasoned Programming.)
Box proofs can be translated into English as follows:
Let c : num with c 0, and let y = sqrt c. Since c 0 (pre-condition of
sqrt), we know y 2 = c.
There are two cases.
If y 0, then
(fourthroot c)4 = (sqrt y)4 = ((sqrt y)2)2
= y2 (because y 0 and so satises the pre-condition of sqrt )
=c
If y < 0, then
(fourthroot c)4 = ((sqrt (;y))2)2
= (;y)2 (because ;y 0)
= y2 = c
Either way, we obtain the required result. 2
However, the virtue of box proofs for beginners is that certain steps are
automatic, and the box proofs give you a framework for making these steps.
They take you to a context where you have disentangled the logic and have
something to prove about concrete programs.
Act 3, Scene 1
Punter: The result of sqrt always seems to be non-negative. Is
that right?
Hacker: looks at code] Yes.
Punter: Good. That's useful to know.
exeunt]
This is how, validly, coding may feed back into the specication. If they
agree on a new, strengthened post-condition:
j (sqrt x)2 ; x j tolerance ^ (sqrt x) 0
then this is better for Punter, so he is happy, and Hacker is no worse
o because his code does it anyway. Punter thinks they have agreed, but
unfortunately Hacker never wrote it into the comments for the sqrt function.
Act 3, Scene 2
It is very late at night. Hacker sits in front of a computer
terminal.]
Hacker: Eureka! I can make sqrt go 0.2% faster by making its
result negative.
Erases old version of sqrt]
Act 3, Scene 3
Punter: My programs have suddenly stopped working.
Hacker: looks at code] It's not my fault. sqrt satises its
specication.
exeunt]
This kind of misunderstanding is just as common when you are your own
customer (that is, when you write your own procedure). It is easy to assume
that you can understand a simple program just by looking at the code but
Another example, min 37
this is dangerous. The code can only tell you what the computer does,
not what the result was meant to be. Avoid the problem with a strong
specication discipline: only assume what is specied. Equivalently, everything
that is assumed must be in the specication.
However, there is an unnatural asymmetry in the way the cases are divided
between xy and x>y, when they could equally well have been x<y and xy.
This case division is not part of what you need to know to be able to use
min. Perhaps a more natural specication would be
3.10 Summary
A specication of a procedure can be expressed as typing information,
pre-condition and post-condition.
You can write these down as part of a Miranda program using logical
notation.
To show that a function denition satises the specication you assume
that you are given arguments satisfying the pre-condition, and show that
the result satises the post-condition.
When you use a function, you rely on its specication, not its denition.
Any change to a specication requires a methodical examination of the
function denition, and all calls of the function. This may entail no
changes, or changes to the program only, or to other specications, or
to both.
3.11 Exercises
1. Write pre- and post-conditions for the functions (both in the text and
the exercises) in Chapter 2. Try to get to the heart of what each
function is meant to achieve.
2. Use pre- and post-conditions to write a specication for calculating
square roots. Try to think of as many ideas as possible for what the
customer might want. Choosing one interpretation rather than another
may be a design decision, or it may call for clarication from the
customer.
3. Suppose you want a procedure to solve the quadratic equation
ax2 + bx + c = 0:
solve :: num -> num -> num -> (num, num)
||pre: ?
||post: x1 and x2 are the solutions of a*x^2+b*x+c = 0,
where (x1,x2) = (solve a b c)
Exercises 39
Assume that you intend to use the formula
q
;b
(b2 ; 4ac)
x= 2a
What are suitable pre- and post-conditions? Try to write them in logic.
(Note: the result type (num, num) is the type of pairs of numbers,
such as (19, 2.6).)
4. Using the uniqueness property of min prove the associative property,
that is,
(Associativity) (min x (min y z)) = (min(min x y) z)
5. Directly from the denition of min prove associativity.
6. Use pre- and post-conditions to write specications for the standard
Miranda functions abs and entier. (Of course, these are already coded
unalterably. Your `specication' expresses your understanding of what
the standard functions do.)
abs takes a number and makes it non-negative by removing its sign: for
instance, abs -1:3 = abs 1:3 = 1:3.
entier takes a number x and returns an integer, the biggest that is no
bigger than x. For instance,
entier 3 = 3 entier 2:9 = 2 entier -3 = -3 entier -2:9 = -3
7. (a) Specify a function round :: num -> num that rounds its argument
to the nearest integer. Try to capture the idea that, of all the
integers, round x is as close as you can get to x.
(b) Show that the denition round1 satises the specication of round:
round1 x = e, if abs (e-x)< abs (e+1-x)
|| i.e. if e is closer to x than e+1
= e+1, otherwise
where e = entier x
(c) Show that this denition round2 computes the same function as
round1.
Every value in Miranda has a type the simplest are num (which you have
already seen), bool and char.
The data type num includes both whole numbers (or integers) and fractional
numbers (or reals, or oating-point numbers). A whole number is a number
whose fractional part is zero. Here are some data values of type num:
56 -78 0 -87.631 0.29e-7 4.68e13 -0.62e-4 12.891
Although there are innitely many numbers, computers have nite capacity
and can only store a limited range. Similarly, within a nite range, there are
innitely many fractional numbers, so not all of them can be stored exactly.
Although such practical limitations can be important when you are doing
numerical calculations, especially when you are trying to obtain a fractional
answer that is as accurate as possible, we shall largely ignore them here. The
theory of numerical analysis deals with these questions.
Booleans are the truth-values True and False and their Miranda type is
called bool. Truth-values are produced as a result of the application of the
comparison operators (for example, >, >=, =, <). They can also be returned
by user-dened functions, for example the function even. Expressions of type
bool are really, rather, like logical formulas, and on this analogy functions
that return a bool as their result are often called predicates.
40
Built-in functions over basic types 41
If the evaluator is presented with an expression which is already in its
normal form, then it will simply echo back the same expression since it cannot
reduce the expression any further. For example,
Miranda False
False
char is the type of characters, the elements of the ASCII character set.
They include printable symbols such as letters ('a', 'A', : : : ), digits ('0'
to '9'), punctuation marks (',', : : : ) and so on, as well as various layout
characters such as newline '\n'. Obviously, characters are most useful when
strung together into lists such as "Reasoned Programming" (note the double
quotes for strings, single quotes for individual characters), so we shall defer
more detailed consideration until the chapter on lists (Chapter 6).
Arithmetic
These operations are on numbers. Each is used as a binary inx operator.
The minus sign can also be used as a unary prex operator.
+ addition
- subtraction
* multiplication
/ division
^ exponentiation
div integer division
mod integer remainder
All except / return exact integer results when arguments are integers, provided
that the integers are in the permitted range. Representation for oating-point
numbers may not be exact, so operations on fractional numbers may not
produce the same results as ordinary arithmetic. For example, (x*y)/y and x
may not be equal. div and mod can be specied in tandem by
42 Functional programming in Miranda
div :: num -> num -> num
mod :: num -> num -> num
||pre: int(x) & int(y) & y ~= 0
|| (where int(x) means x is a whole number and ~ means not)
||post: x = (x div y) * y + (x mod y)
|| & y>0 -> (0 <= (x mod y) < y)
|| & y<0 -> (y < (x mod y) <= 0)
Arithmetic expressions can be entered directly into the evaluator, for example
after the computer has displayed the Miranda prompt:
Miranda 14 div 5
2
Miranda 14 mod 5
4
Miranda 2^4
16
The relative precedence of these operators is as follows:
+ -
* / div mod + increasing precedence
^
Function application always binds more tightly than any other operator!
Parentheses are used when one is not sure of binding powers or when one
wishes to force a dierent order of grouping, for example,
Miranda double 5 + 8 mod 3 = 10+2
12
Miranda double (5 + 8) mod 3 = double 13 mod 3 = 26 mod 3
2
Comparisons
= equals
= not equals All have the same level of precedence.
< less than
> greater than
<= less than or equal Their precedence is lower
>= greater than or equal than that of the arithmetic operators.
Comparison operators are made up of relational operators ( >, >=, <, <=) and
equality operators (=, =) and their result is of type bool. The following are
some examples:
Built-in functions over basic types 43
Miranda 5 = 9
False
Miranda 6 >= 2+3
True
As the second example suggests, the precedence of comparison operators is
lower than that of the arithmetic operators. Note that comparison operators
cannot be combined so readily for example, the expression (2<3<4) would
give a type error since it would be interpreted as
((2<3)<4) = True<4 .
When operating on numbers `=' may not return the correct result unless
the numbers are integers in the permitted range. This is because fractional
numbers should be compared up to a specic tolerance. For example,
Miranda sqrt(2)^2 = 2
False
We can dene a function within as follows:
within eps x y = abs(x-y) < eps
within can then be used instead of `=' when comparing fractional numbers to
a certain tolerance. For example, (within 0.001 a b) can be used to see if
a and b are closer than 0:001 apart.
Logical operators
Boolean values may be combined using the following logical operators:
& conjunction (logical ^ `and') in order of
\/ disjunction (logical _ `or') + increasing precedence
negation (logical : `not')
Their precedence is lower than that of comparisons. They can be dened in
Miranda itself (not that you will need to do this) as in Figure 4.1. Dening
these primitives in Miranda not only gives their meaning but also illustrates
the use of pattern matching with Booleans. Exercise: we have used one
equation to dene and and two for or. Try writing and with two equations
and or with one.
It is always a good idea to use parentheses whenever | as is often the case
with logical connectives | there is the slightest doubt about the intended
meaning:
Miranda 4>6 & (3<2 \/ 9=0)
False
44 Functional programming in Miranda
and :: bool -> bool -> bool
||pre: none
||post: and x y = x & y
or :: bool -> bool -> bool
||pre: none
||post: or x y = x \/ y
not :: bool -> bool
||pre: none
||post: not x = ~x
and x y = y, if x
= False, otherwise
or True x = True
or False x = x
Figure 4.1
4.3 User-dened functions
Identiers
Before introducing a new function the programmer must decide on an
appropriate name for it. Names, also called identiers, are subject to some
restrictions in all programming languages.
Throughout a program, identiers are used for variables, function names
and type names. In Miranda, identiers must start with a lower case letter.
The remaining characters in the identier can be letters, digits, , or '
(single quote). However, not all such identiers are valid as there are a
number of special words (reserved words) which have a particular meaning to
the evaluator, for example where, if, otherwise. Clearly, the programmer
cannot use a reserved word for an identier as this would lead to ambiguities.
Furthermore, there are also a number of predened names (for example, those
of built-in functions such as div, mod) which must be avoided.
Meaningful identiers for functions and variables will make a program easier
to read. Longer names are usually better than shorter names, although the
real criterion is clarity. For example, the identier record is probably a
better choice than r. But deciding whether it is better than, say, rec is
not as straightforward. In fact, in most cases modest abbreviations need not
User-dened functions 45
reduce the clarity of the program.
A good rule is that identiers should have long explanatory names if they
are used in many dierent parts of the program. This is because it may
be dicult to refer to the denition if it is a long way from the use. On
the other hand, identiers with purely local signicance can safely have short
names | such as x for a function argument. If the variable in question is
a general purpose one then nothing is gained by having a long name such
as theBiggestNumberNeeded an identier such as n may be just as clear.
Finally, it is worth mentioning that it is best to avoid acronyms for identiers.
For example, tBNN is even worse than theBiggestNumberNeeded.
Dening values
It is often useful to give a name to a value because the name can then be
used in other expressions. For example, we have already seen the denition of
mypi:
mypi :: num
mypi = 3.14159
As usual, the choice of meaningful names will make the program easier to
read. The following is a simple example of an expression which involves names
that have been previously dened using `=':
hours_in_day = 24
days = 365
hours_in_year = days * hours_in_day
If you are already familiar with imperative languages such as Pascal or Basic,
then it is important to understand that a denition like this is not like an
assignment to a variable, but, rather, like declaring a constant. The identier
days has the value 365 and this cannot be changed except by rewriting the
program. What is more, if you have conicting denitions within a program,
then only the rst will ever have any eect. At this point it may also appear
natural to be able to give names not only to values such as numbers or
truth-values but also to functions, for example
dd :: num -> num
dd = double
dd behaves identically to double in every respect. This indicates that functions
are not only `black boxes' that map values to other values, but are values in
themselves. Thus in functional languages functions are also rst-class citizens
(just like numbers, Booleans, etc.) which can be passed to other functions
as parameters or returned as results of other functions. This is discussed in
much more detail in Chapter 8.
46 Functional programming in Miranda
Thus entering a function's name without any parameters is the equivalent
of entering a value. However, the major dierence between a function value
and other values is that two functions may not be tested for equality. This is
the case even if they both have precisely the same code or precisely the same
mappings for all possible input values. Thus the expression (dd = double)
will result in an error.
Dening functions
In Miranda, new functions are introduced in three steps:
1. Declare the function name and its type (its argument and result types):
square :: num -> num
2. Provide the appropriate pre- and post-conditions:
||pre: none
||post: square n = n^2
Miranda (>) 8 9
False
Conversely, user-dened binary functions can also be applied in an inx form
by prexing their name with the special character $:
Miranda 9 $smaller 8
8
Local denitions
In mathematical descriptions one often nds expressions that are qualied by
a phrase of the form `where : : : '. The same device can also be used in
function denitions. For example, balance*i where i = interestRate/100.
In fact, we have already used where in the denition of fourthroot in
Chapter 3. The special reserved word where can be used to introduce local
denitions whose context (or scope) is the expression on the entire right-hand
side of the denition which contains it. For example,
f x y = x + a, if x > 10
= x - a, otherwise
where a = square (y+1)
In any one equation the where clause is written after the last alternative.
Summary 51
Its local denitions govern the whole of the right-hand side of that equation,
including the guards, but do not apply to any other equation.
Furthermore, following a where there can be any number of denitions.
These denitions are just like ordinary denitions and may therefore contain
nested wheres or be recursive denitions.
Note that the whole of the where clause must be indented, to show that it
is part of the right-hand side of the equation. The evaluator determines the
scopes of nested wheres by looking at their indentation levels. In the next
example it is clear that the denition of g is not local to the right-hand side
of the denition of f, but those of y and z are
f x = g y z
where y = (x+1) * 4
z = (x-1) * x
g x z = (x + 1) * (z-1)
Let us consider some uses of local denitions. Firstly, as in fourthroot, they
can be used to avoid repeated evaluation. In an expression a subexpression
may appear several times, for example
z+(smaller x y)*(smaller x y)
If you like, you can view this use of local denitions as a mechanism for
extending the existing set of formal parameters.
Local denitions can also be used to decompose compound structures or
user-dened data types by providing names for components (as will be seen
later, in Chapter 7).
It is good programming practice to avoid unnecessary nesting of denitions.
In particular, use local denitions only if logically necessary. Furthermore,
a third level of denition should be used only very occasionally. Failure to
follow these simple programming guidelines will result in denitions that are
dicult to read, understand and reason about.
4.5 Summary
Miranda has three primitive data types: numbers, truth-values and
characters (num, bool and char respectively).
52 Functional programming in Miranda
Miranda also provides many built-in operators and functions.
A new function is dened in three stages. The function's type is
declared, the function is specied in a comment and then it is dened
using one or more equations.
Although type declarations and specications are not mandatory for
functions, it is good programming practice to include them with all
denitions.
Miranda is layout-sensitive in that it assumes that the entire right-hand
side of an equation lies directly below or to the right of the rst symbol
of the right-hand side (excluding the initial =). This is the oside rule.
To aid in the portability of programs try, wherever possible, to write
order-independent code. This means writing mutually exclusive guards
or patterns.
Functions (or other values) can also be dened locally to a denition.
Such local denitions can be used to avoid repeated evaluation or to
decompose compound structures, as will be seen in Chapter 7.
4.6 Exercises
1. Write denitions for the functions specied in the exercises at the end
of Chapter 3.
2. Dene istriple, which returns whether the sum of the squares of two
numbers is equal to the square of a third number. A Pythagorean triple
is a triple of whole numbers x y and z that satisfy x2 + y2 = z2. The
Miranda function istriple should be declared as follows:
istriple :: num -> num -> num -> bool
||pre: none
||post: (istriple a b c) <-> a,b,c are the lengths of the
|| sides of a right angle triangle
The function takes as arguments three numbers and returns true if they
form such a triple. Evaluate the function on the triples
3 4 5
5 12 13
12 14 15
and check that the rst two are Pythagorean triples and the third is
not. Do this exercise twice: rst assume that c is the hypotenuse and
then rewrite it so that any of the parameters could be the hypotenuse.
Chapter 5
5.1 Recursion
Pn sum n which gives us the sum of the
Suppose we want to write a function
natural numbers up to n, that is, i=0 i:
sum n = 0 + 1 + 2 + 3 + : : : + (n ; 1) + n
Inspecting the above expression we see that if we remove `+n' we obtain an
expression which is equivalent to sum(n ; 1), at least if n 1.
This suggests that
sum n = sum (n ; 1) + n (5.1)
We say that the equation exhibits a recurrence relationship. To complete
the denition we must dene a base case which species where the recursion
process should end. For sum this is when the argument is 0. Thus the
required denition is
sum :: num -> num
||pre: nat(n)
||post: sum n = sum(i=0 to n) i
sum n = 0, if n = 0
= sum (n-1) + n, if n > 0
`sum(i=0 to n) i' is intended to be a typewriter version of `Pni=0 i'. If we
just used the recurrence relation (5.1), forgetting the base case, then we would
obtain non-terminating computations as illustrated in Figure 5.1. Function
denitions, like that of sum, that call themselves are said to be recursive.
Obviously, the computation of sum involves repetition of an action.
Often when describing a function | such as sum | there are innitely
many cases to consider. In conventional imperative programming languages
this is solved by using a loop, but in functional languages there are no
explicit looping constructs. Instead, solutions to such problems are expressed
53
54 Recursion and induction
sum 3
(sum 2) + 3
(sum 1) + 2 + 3
(sum 0) + 1 + 2 + 3
(sum -1) + 0 + 1 + 2 + 3
:
:
a black hole
Figure 5.1
by dening a recursive function. Clearly, the recursive call must be in terms
of a simpler problem | otherwise the recursion will proceed forever.
The example given above illustrated the technique of writing recursive
functions, which can be summarised as follows:
1. Dene the base case(s).
2. Dene the recursive case(s):
(a) reduce the problem to simpler cases of the same problem,
(b) write the code to solve the simpler cases,
(c) combine the results to give required answer.
square 4
square n = n*n
Figure 5.2
the next reduction (from the set of possible ones) in a consistent way. This
is called the evaluator's reduction strategy. We will not discuss reduction
strategies here except to mention that Miranda's reduction strategy is called
lazy evaluation. Lazy evaluation works as follows:
Reduce a particular part only if its result is needed.
Therefore, because of lazy evaluation you can write function denitions such
as
f n = 1, if n = 0
= n * y, otherwise
where y = f(n-1)
Although the scope of the local denition of y is the entire right-hand side of
the equation for f, we know that by lazy evaluation y will only be evaluated
if it is needed (that is, if and only if the rst guard fails).
zjx , 9y : nat: (x = z y)
When we write `y : nat', we are using the predicate nat as though it were
a Miranda type, though it is not. You can think of `nat(y)' and `y : nat'
as meaning exactly the same, namely that y is a natural number. But
the type-style notation is particularly useful with quantiers:
9y : nat: P means 9y: (nat(y) ^ P )
(`there is a natural number y for which P holds')
8y : nat: P means 8y: (nat(y ) ! P )
(`for all natural numbers y P holds')
Proposition 5.1 For any two natural numbers x and y, there is at most one
z satisfying the specication for (gcd x y).
Proof Let z1 and z2 be two values satisfying the specication for (gcd x y)
we must show that they are equal. All common divisors of x and y divide z2,
so, in particular, z1 does. Similarly, z2 divides z1. Hence for some positive
natural numbers p and q, we have z1 = z2 p z2 = z1 q, so z1 = z1 p q
It follows that either z1 = 0, in which case also z2 = 0, or p q = 1, in which
case p = q = 1. In either case, z1 = z2. 2
Note that we have not actually proved that there is any value z satisfying
the specication only that there cannot be more than one. But we shall soon
have an implementation showing how to nd a suitable z, so then we shall
know that there is exactly one possible result.
Euclid's algorithm relies on the following fact.
Proposition 5.2 Let x and y be natural numbers, y 6= 0. Then the common
divisors of x and y are the same as those of y and (x mod y).
Recursion variants 57
Proof For natural numbers x and y there are two fundamental properties of
integer division, which in fact are enough to specify it uniquely: if y 6= 0
(pre-condition), then (post-condition)
x = y (x div y) + (x mod y)
0 (x mod y) < y
Suppose n is a common divisor of y and (x mod y). That is, there is a p
such that y = n p and a q such that (x mod y) = n q. Then
x = y (x div y) + (x mod y) = n (p (x div y) + q)
so n also divides x. Hence every common divisor of y and (x mod y) is also a
common divisor of x and y. The converse is also true, by a similar proof. 2
It follows that, provided y 6= 0 (gcd x y) must equal (gcd y (x mod y)).
(Exercise: show this.) On the other hand, (gcd x 0) must be x. This is
because x j x and x j 0, and any common divisor of x and 0 obviously divides
x, so x satises the specication for (gcd x 0). We can therefore write the
following function denition:
gcd x y = x, if y=0
= gcd y (x mod y), otherwise
Question: does this denition satisfy the specication?
Let us follow through the techniques that we discussed in Chapter 3. Let
x and y be natural numbers, and let z = (gcd x y). We must show that
z has the properties given by the post-condition, and there are two cases
corresponding to the two clauses in the denition:
y = 0 : z = x We have already noted that this satises the specication.
y 6= 0 : z = (gcd y (x mod y)) What we have seen shows that provided that z
satises the specication for (gcd y (x mod y)), then it also satises the
specication for (gcd x y), as required.
2
But how do we know that the recursive call gives the right answer? How do
we know that it gives any answer at all? (Conceivably, the recursion might
never bottom out.) Apparently, we are having to assume that gcd satises its
specication in order to prove that it satises its specication.
Simple induction
The ingredients of a simple induction proof are as follows:
a predicate P or property on the natural numbers for which you wish
to prove 8n : nat: P (n) (P holds for all natural numbers n)
the base case: a proof of P (0)
the induction step: a proof of 8n : nat: (P (n) ! P (n + 1)), in other
words a general method that shows for all natural numbers n how, if
you had a proof of P (n) (the induction hypothesis), you could prove
P (n + 1).
Given these, you can indeed deduce 8n : nat: P (n). This is the Principle
of Mathematical Induction. The separate parts can be put in the box
proof format, as can be seen in Figure 5.3. If you were using ordinary
`forall-arrow-introduction', as in Chapter 17, you would produce a box proof
such as that given in Figure 5.4. You could then consider two cases, M = 0
and M = N + 1 for some N , and so you end up more or less as in induction,
proving P (0) and P (N + 1). However, in induction, you have a free gift, the
induction hypothesis P (N ), as an extra assumption. Without it, the proof
would be dicult or even impossible.
Mathematical induction 61
..
. N : nat P (N )
..
P (0) base case . induction step
P (N + 1)
8n : nat: P (n) simple induction
M : nat
..
.
P (M )
8n : nat: P (n) 8I
Figure 5.4
To show how this works, suppose, for instance, you want to prove P (39976).
The ingredients of the induction show that you can rst prove P (0) from
this you can obtain a proof of P (1) from this a proof of P (2) and so on up
to P (39976). Of course, you never need to go through all these steps. It is
sucient to know that it can be done, and then you know that P does hold
for 39976.
Another way of justifying the induction principle is by contradiction: if
8n : nat: P (n) is false, then there is a smallest n for which P (n) is false.
What is n? Certainly not 0, for you have proved the base case. So taking
N = n ; 1, which is still a natural number, we have P (N ) because n was the
smallest counter-example. But now the induction step shows how to prove
P (N + 1), that is, P (n), a contradiction. The following is a simple example.
N : nat
8m : nat: (m < N ! P (m)) induction hypothesis
..
.
P (N )
8n : nat: P (n) course of values induction
Figure 5.5
If n is itself prime, then we are done. (This also deals with the special
case n = 1 for which there are no positive natural numbers < n.) If n is not
prime, then we can write n = p q for some natural numbers p and q, neither
of them equal to 1. Then p and q are both less than n, so by induction each
is a product of primes. Hence n is, too. 2
We have actually cheated here in order to illustrate the technique in an
uncomplicated way. The proof does not illustrate course of values induction on
the natural numbers, but a similar principle on the positive natural numbers.
The correct proof proves the property P (n) dened by
P (n) def
= (n > 0 ! n is a product of primes)
Then there are two cases. If n = 0, then P (n) is trivially true
(`false ! anything' is always true). Otherwise, n > 0, when we use the proof
as given. When we reach n = p q, p and q must both be positive, so that
from P (p) and P (q) we deduce that p and q are both products of primes. 2
This example shows a common feature of course of values induction. It
proves P for n by reducing to simpler cases (p and q, both smaller than n),
which we assume have already been done.
y < y or
0
y = y and x < x
0 0
You could say that y is almost a recursion variant, certainly it never increases
in recursive calls (unlike x). But in the case where y remains unchanged as a
variant, it must be helped by x decreasing.
There is a quite general principle of well-founded induction (see Appendix A)
that uses this idea, but, rather than going into the generalities, here we shall
show how to use a double induction.
Proposition 5.7 This denition of gcd satises the specication.
Proof We use course of values induction to prove 8y : nat: P (y), where
P (y) def
= 8x : nat: ((gcd x y) terminates and satises its post-condition)
Therefore let us take a natural number Y , and assume that P (y) holds for
all y < Y . Having xed our Y , we now use course of values induction again
to prove P (Y ), that is, 8x : nat: Q(x), where
Q(x) def
= (gcd x Y ) terminates and satises the post-condition.
Therefore, let us now take a natural number X , and assume that Q(x)
holds for all x < X . We prove Q(X ). There are three cases, as follows, for
the three alternatives in the denition of gcd:
5.8 Exercises
1. The factorial of a non-negative integer n is denoted as n! and dened as:
factorial n def
= (n ; 1) (n ; 2) (n ; 3) : : : 2 1
0! is dened to be 1. Write a function factorial to dene the factorial
of a non-negative integer. Ignore the possibility of integer overow.
66 Recursion and induction
2. Write a function remainder which denes the remainder after integer
division using only subtraction. Ignore the possibility of division by zero.
3. Write a function divide which denes integer division using only addition
and subtraction. Ignore division by zero.
4. Here are some exercises with divisibility: show for all natural numbers
x y and z that
(a) 1 j y (b) x j y ^ x j z ^ y z ! x j (y ; z)
(c) x j 0 (d) x j y ^ y j z ! x j z
(e) x j x (f) x j y ^ x j z ! x j (y + z)
(g) 0 j y $ y = 0 (h) x j y ^ y j x ! x = y
5. (a) Use the method of `multiplication without multiplying' to compute
exponentiation, power x n= xn , making use of the facts that
xn = xn div 2 xn div 2 if n is even
and
xn = xn div 2 xn div 2 x if n is odd
(b) Write a Miranda function, multiplications, that computes the
number of multiplications performed by power(x, n) given the value
of n. How would this compare with the corresponding count of
multiplications for a more simple-minded recursive calculation of xn,
using xn+1 = xn
x?
6. (Tricky) Specify and dene a function middle to nd the middle one of
three numbers. Prove that the denition satises its specication.
7. Prove that the principles of simple induction and course of values
induction are equivalent. In other words, though course of values
induction looks stronger (can prove more things), it is not.
First, show that any simple induction proof can easily be converted into
a course of values induction proof.
Second, show that if you have a course of values induction proof of
8n : nat: P (n) then its ingredients can be used to make a simple
induction proof of 8n : nat: (8m : nat: (m < n ! P (m))), and that this
implies 8n : nat: P (n).
8. Newton's method for calculating a square root px works by producing
a sequence y0 y1 : : : of better and better approximations to the answer,
where
yn+1 = 21 (yn + yx )
n
The starting approximation y0 can be very crude | we shall use x + 1.
We shall deem yn accurate enough when j yn2 ; x j< epsilon, epsilon
being some small number dened elsewhere in the program (for instance,
epsilon = 0.01). Here is a Miranda denition:
Exercises 67
newtonsqrt::num -> num
||pre: x >= 0 & epsilon > 0
||post: abs(r*r - x) < epsilon & r >= 0
|| where r = newtonsqrt x
newtonsqrt x = ns1 x (x+1)
ns1::num -> num -> num
||pre: x >= 0 & epsilon > 0
|| & a >= 0 & a*a >= x & (a = 0 -> x = 0)
||post: abs(r*r - a) < epsilon & r >= 0
|| where r = ns1 x a
ns1 x a = a, if a*a - x < epsilon
= ns1 x ((a + x/a)/2), otherwise
(The last three pre-conditions of ns1 need some thought. a 0 looks
reasonable enough, a = 0 ! x = 0 avoids the risk of dividing by zero,
and a2 x is not strictly necessary but, as we shall see, it makes it
easier to nd a recursion variant.)
(a) Show that newtonsqrt and ns1 satisfy their specication, assuming
that the recursive call in ns1 works correctly. This is easy, and the
proof is nished once we have found a recursion variant that is the
dicult part!
(b) If x 0, a2 x and b = 21 (a + xa ) (for instance, if a = yn and
b = yn+1), show that
0 b2 ; x = 1 (1 ; x2 )(a2 ; x) 1 (a2 ; x)
4 a 4
(c) The basis for a recursion variant is a2 ; x. As this gets smaller,
the approximation gets better and we are making progress towards
the answer. However, as it stands it cannot be a recursion variant
because it is not a natural number. (Unlike the case with natural
numbers, a positive real number can decrease strictly innitely many
times, by smaller and smaller amounts.) Use (b) to show that a
suitable variant is
max(0 1 + entier(log4
a2 ; x ))
epsilon
(This gives a number that | by (b) | decreases by at least 1
each time, entier turns it into an integer, and dividing a2 ; x by
epsilon ensures that this integer is a natural number except for the
last time round, which is coped with by max(0 1 + : : :).)
Chapter 6
Lists
6.1 Introduction
The various data types encountered so far, such as num and bool, are capable
of holding only one data value at a time. However, it is often necessary to
represent a number of related items of data in some way and then be able
to have a single name which refers to these related items. What is required
is an aggregate type, which is a data type that allows more than one item
of data to be referenced by a single name. Aggregate types are also called
data structures since they represent a collection of data in a structured and
orderly manner.
In this chapter we introduce the list aggregate type, together with the
various predened operators and functions in Miranda that manipulate lists.
We shall also see how to use lists of characters to represent strings.
The third example is a valid list since the elements of the list have the
68
The list aggregate type 69
same type they are all lists of numbers. The empty list !], which has no
elements, is rather special because it could be of type !*], where the symbol
* represents any type. (In fact, if you enter !]:: in Miranda, which asks for
the type of !], the system will respond !*].) Similarly, the fourth example
illustrates a valid list since all its elements have the same type, namely
functions that map two numbers to a number.
A list !x] with just one element is known as a singleton list. Two lists
are equal if and only if they have the same values with the same number of
occurrences in the same order. Otherwise they are dierent, so the lists
!1,2] !2,1] !1,1,2] !1,2,1] !2,1,1]
are all dierent even though they have the same elements 1 and 2.
Concatenation
The most important operator for lists is ++ (called concatenate or append),
which joins together two lists of the same type to form a single composite
list. For example,
!1,2,3]++!1,5] = !1,2,3,1,5]
We shall see shortly that there is another method for building up lists, called
cons none the less ++ is usually conceptually more natural, and it is often
useful in specications. We can formalize the condition that a value x is an
element of a list xs as
9us vs: (xs = us++!x]++vs)
Note that, like + and *, ++ is associative: the equation
xs++(ys++zs) = (xs++ys)++zs
always holds, and so you might as well write xs++ys++zs. In fact, there is no
need for brackets for any number of lists appended together. Concatenating
any list xs with the empty list !] returns the given list. This is called the
unit law and !] is the unit (just like 0 for + or 1 for *) with respect to ++:
xs++!] = !]++xs = xs
List deconstruction
The function hd (pronounced head) selects the rst element of a list, and
tl (pronounced tail) selects the remaining portion:
hd !1,2,3] = 1
tl !1,2,3] = !2,3]
70 Lists
Notice the type dierence | the result of hd is an element, that of tl is
another list. It is an error to apply either of these functions to an empty
list, and so appropriate tests must be carried out (using guards or pattern
matching) to avoid such errors.
Cons
The cons (for construct) operator : is an inverse of hd and tl. It takes a
value and a list (of matching types) and puts the value in front to form a
new list, for example,
1:!2,3,4] = !1,2,3,4] = 1:2:3:4:!]
x:xs = !x]++xs
hd (x:xs) = x
tl (x:xs) = xs
xs = (hd xs):(tl xs), if xs ~= !]
Cons as constructor
From the human point of view, there is often nothing to indicate that one end
of a list should be given any preference over the other. However, functional
programming interpreters store the elements in a manner such that those
elements from one end are much more accessible than those from the other.
Imagine a list as having its elements all parcelled up together, but in a
nested way. If you unwrap the parcel you nd just one element, the head,
and another parcel containing the tail. (The empty list is special, of course.)
The further down the sequence a value is, the more dicult it is to get out,
because you have to unwrap more parcels.
From this point of view, the most accessible element in a list is the rst,
that is, the leftmost in the !: : : ] notation.
Storing a list !x0,x1,x2,: : :,xn] in this way corresponds notationally to
writing it, using cons, as x0:x1:x2:: : ::xn:!] and the way the function cons
is applied in the computer, for example to evaluate x:xs, does not perform
any real calculations, but, rather, just puts x and xs together wrapped up
in a wrapper that is clearly marked `:'. (The empty list is just a wrapper,
marked `empty'.) A function implemented in this way is called a `constructor'
function, and there are some more examples in Chapter 7. Obviously, a
crucial aspect is that you can unwrap to regain the original arguments, so it
is important that : is `one-to-one' | dierent arguments give dierent results
| or, more formally,
8x y xs ys: (x:xs = y : ys ! x = y ^ xs = ys)
++ is not one-to-one and so could never be implemented as a constructor
function, but snoc, dened by
72 Lists
snoc !] = !]
snoc xs x = xs++!x]
is one-to-one and could have been implemented as a constructor function for
lists instead of :, but it is not.
Notice how pattern matching does not just express implicit tests on the
actual arguments (Are they empty or non-empty? Is the wrapper marked
empty or cons?) as we saw in Section 4.4 it also provides the right-hand side
of the equation with names for the unwrapped contents of the arguments.
Innite lists
Some lists in Miranda can be innite, such as the following examples:
zeros = 0:zeros || = !0,0,0,...]
nandup n = n:(nandup (n+1)) || = !n,n+1,n+2, ] :: :
cards = nandup 0 || = !0,1,2,3, ] :::
Some calculations using these will be potentially innite, and you will need
to press control-C when you have had enough. For instance, evaluating zeros
or cards will start to produce an innite quantity of output, and evaluating
#zeros or #cards will enter an innite loop.
However, the lazy evaluation of Miranda means that it will not go into
innite computations unnecessarily. For instance, hd (tl cards) gives 1 as its
result and stops.
Now the problem is that we thought we had proved that length xs always
terminates, because it has a recursion variant #xs length zeros does not
74 Lists
terminate, and this is because the variant #zeros is undened (or innite,
which is just as bad). The moral is:
Our reasoning principles using recursion variants only work for
nite lists.
This is a shame because innite lists can be useful and well-behaved in
fact research into nding the most convenient ways of reasoning about innite
lists is ongoing. However, we shall only deal with nite lists and shall make
the implicit assumption | usually amounting to an implicit pre-condition |
that our lists are nite. Then we can use their lengths as recursion variants,
and the `circular reasoning' technique for recursion works exactly as before.
Another example
The following is a less trivial example. It tests whether a given number
occurs as an element of a given list of numbers. Note how this condition can
be expressed precisely using ++ in the specication. If x is an element of xs,
then xs can be split up as us++!x]++vs, where us and vs are the sublists of
xs coming before and after some occurrence of x:
isin :: num -> !num] -> bool
||pre: none
||post: isin x xs <-> (E)us,vs:!num]. xs = us++!x]++vs
||recursion variant = #xs
isin x !] = False
isin x (y:ys) = True, if x = y
= isin x ys, otherwise
The recursion variant in isin x xs is #xs, and we can reason that isin x xs
works correctly as follows.
Proposition 6.1 isin x meets its specication. If xs = !], then we cannot
possibly have xs = us++!x]++vs, for that would have length at least 1. Hence
the result False is correct.
If xs has the form (y:ys) then note that, from the denition, isin
x (y:ys) (x = y) _ isin x ys. Hence we must prove
(x = y) _ isin x ys $ 9us vs: ((y:ys) = us++!x]++vs)
assuming that the recursive call works correctly. For the ! direction, we have
the following two cases:
1. If x = y then (y:ys) = !]++!x]++ys.
2. If isin x ys then by induction ys = U++!x]++V for some U and V and
so y:ys = (y:U)++!x]++V.
Trapping errors 75
For the direction, we have (y:ys) = U++!x]++V for some U and V (not
necessarily the same as before). If U = !] then y = x, while if U 6= !] then
ys = (tl U)++!x]++V and so isin x ys by induction. 2
Although this may look a little too much like hard work, something of
value has been achieved. The post-condition is very much a global property of
the function | a property of what has been calculated rather than how the
calculation was done. It is tempting to think of the function denition itself
as a formal description of what the intuition `x is an element of the list xs'
means, but actually the specication comes closer to the intuitive idea. You
can see this if you think how you might prove such intuitively obvious facts
as `if x is in xs then it is also in xs++ys and ys++xs for any ys' | this is
immediate from the specication, but less straightforward from the denition.
Let us note one point that will be dealt with properly in Chapter 7, but is
useful already. You could replace num in isin by char or bool or !num] or
any other type at all to give other versions of isin, but the actual denition
would not suer any changes whatsoever: it is `polymorphic' (many formed),
and it is useful to give its type `polymorphically' as * -> !*] -> bool, leaving
* to be replaced by whatever type you actually want. Indeed, Miranda itself
understands these polymorphic types.
Sortedness
Let us start by specifying when a list is sorted (in ascending order) | if
xs = !x0 x1 x2 : : : xn ] then we write Sorted(xs) to mean that informally
x0 x1 x2 : : : xn
This can be formalized quite straightforwardly using the subscripting
operator ! but another way, using ++, is as follows:
Sorted(xs) def
= 8us vs : !
]: 8a b :
: xs = us++!a b]++vs ! a b
In other words, whenever we have two adjacent elements a and b in xs (with
a rst), then a b.
Note that we used a polymorphic type | we wrote * for the type of
the elements, !*] for that of the lists. Of course, it only makes sense to
call a list sorted if we know what means for its elements. It is obvious
how to do this when their type is num, but Miranda understands for
many other types. For instance, values of type char have a natural ordering
(by ascii code), and this is extended to strings (values of type !char]) by
lexicographic ordering and to values of other list types by the same method.
The sorting algorithm works `polymorphically' | it does not depend on the
An example | insertion sort 77
type. We shall therefore express its type using *, but remember (as implicit
pre-conditions) that * must represent a type for which is understood.
Let us prove some useful properties about sortedness.
Proposition 6.2
1. The empty list !] and singleton !x] are sorted.
2. !x y] is sorted i x y.
3. If xs is sorted, then so is any sublist ys (that is, such that we can write
xs = xs1 ++ys++xs2 for some lists xs1 and xs2 ).
4. Suppose xs++ys and ys++zs are both sorted, and ys is non-empty. Then
xs++ys++zs is sorted.
Proof
1. This is obvious, because the decomposition xs = us++a b]++vs can only
be done if #xs 2.
2. This is obvious, too.
3. If ys = us++!a b]++vs, then xs = (xs1++us)++!a b]++(vs++xs2), and so
a b because xs is sorted.
4. Suppose xs++ys++zs = us++!a b]++vs. It is clear that a and b are either
both in xs ++ys or both in ys ++zs, and so a b.
2
The third case, set out in full using box notation (Chapters 16 and 17),
can be seen in Figure 6.2.
xs is sorted
8a b us vs: (xs = us++a b]++vs ! a b)
xs = xs1++ys++xs2 def of sublist
assumption
8I A B US VS
ys = US++A B ]++VS
xs = xs1 ++US++A B ]++VS++xs2 def sublist
AB assoc of ++ and 8!E
ys = US++A B ]++VS ! A B !I
8a b us vs: (ys = us++a b]++vs) ! a b 8I
ys is sorted def
Figure 6.2
78 Lists
When we sort a list, we obviously want the result to be sorted, and this will
be specied in the post-condition. The other property that we need is that
the result has the same elements as the argument, but possibly rearranged |
the result is a permutation of the argument.
Let us write Perm(xs,ys) for `ys is a permutation of xs'. We shall not
dene this explicitly in formal terms, but use the following facts:
Perm(xs,xs)
Perm(xs,ys) ! Perm(ys,xs)
Perm(xs,ys) ^ Perm(ys,zs) ! Perm(xs,zs)
Perm(us++vs++ws++xs++ys us++xs++ws++vs++ys), that is, vs and xs are
swapped
In fact, any permutation can be produced by a sequence of swaps of adjacent
elements. We are now ready to specify the function sort:
sort :: !*] -> !*]
||pre: none (but, implicitly, there is an ordering over *)
||post: Sorted(ys) & Perm(xs,ys)
|| where ys = sort xs
Recall that the method of insertion sort was to sort x:xs by rst sorting xs
and then inserting x in the correct place. We therefore dene
sort !] = !]
sort (x:xs) = insert x (sort xs)
The following is an example of how we intend sort to evaluate:
sort !4, 1, 9, 3]
= insert 4 (sort !1, 9, 3])
= insert 4 (insert 1 (sort !9, 3]))
= insert 4 (insert 1 (insert 9 (sort !3])))
= insert 4 (insert 1 (insert 9 (insert 3 (sort !]))))
= insert 4 (insert 1 (insert 9 (insert 3 !])))
= insert 4 (insert 1 (insert 9 !3]))
= insert 4 (insert 1 !3, 9])
= insert 4 !1, 3, 9]
= !1, 3, 4, 9]
Specifying insert
Implementing insert
The idea in insert a xs is that we must move past all the elements of xs
that are smaller than a (they will all come together at the start of xs) and
put a in front of the rest. Hence there are two cases for insert a (x :xs):
the head is either a or x, according to which is bigger, and if a is bigger then
it must be inserted into xs:
||insert was specified above
insert a !] = !a]
insert a (x:xs) = a:x:xs, if a <= x
= x:(insert a xs), otherwise
for example,
insert 3 !1,4,9] = 1:(insert 3 !4,9]) = 1:3:4:!9] = !1,3,4,9]
reverse
The reverse function is dened as follows:
List induction 83
1 xy
2 Merge(xs y:ys, ws)
3 ws is sorted x:xs is sorted assumptions
8I US,VS,A,B 4
5 x:ws=US++A,B]++VS
6 US = ] _ US 6= ]
7 case 1 of _E
8 US = ]
9 x=A
10 ws = B :VS
11 B = hd xs _ B = y def Merge
12 B = hd xs B=y
13 x:xs = ]++x B ]++tl xs x y assumed
14 xB (x:xs sorted) A B eqsub
15 AB eqsub
16 AB _E (11)
17 case 2 of _E
18 US 6= ]
19 ws = tl US++A B ]++VS
20 AB (ws sorted)
21 AB _E (6)
22 x:ws = US++A B ]++VS ! A B !I
23 x:ws is sorted 8I
Figure 6.3
reverse :: !*] -> !*]
||pre: none
||post: reverse xs is the reverse of xs
||recursion variant for reverse xs is #xs
reverse !] = !]
reverse (x:xs) = (reverse xs)++!x]
It is not clear how this function ought to be specied. But bearing in
84 Lists
mind that the specication is supposed to say how we can make use of the
function, and bearing also in mind our idea that ++ is more useful than cons
in specications because it does not prefer one end of the list to the other,
let us try to elaborate the specication by giving some useful properties of
the function:
(reverse !]) = !]
(reverse !x]) = !x]
(reverse (xs++ys)) = (reverse ys)++(reverse xs)
These are enough to force the given denition, for we must have
reverse (x:xs) = reverse (!x]++xs)
= (reverse xs)++(reverse !x])
= (reverse xs)++!x]
There still remains the question of whether the denition does indeed satisfy
these stronger properties. The rst two are straightforward from the denition,
but the third is trickier. It is certainly not obvious whether the recursion
variant method gives a proof.
Application to reverse
2
Note how although we have two lists to deal with, xs and ys, in this example
we only need to use induction on one of them: xs. If you try to prove the
result by induction on ys, you will nd that the proof just does not come out.
To illustrate the advantage of using our stronger properties (Proposition 6.5)
instead of just the denition, let us prove the intuitively obvious property
that if you reverse a list twice you get the original one back. If you try to
prove this directly from the denition, you will nd that it is not so easy.
Proposition 6.6 Let xs be a list. Then (reverse (reverse xs)) = xs
Proof We use list induction on xs.
base case: xs = !] reverse (reverse !]) = (reverse !]) = !]
induction step: When the list is not empty,
(reverse (reverse(x:xs)))
= (reverse ((reverse xs) ++!x]))
= (reverse !x])++(reverse (reverse xs))
= !x]++xs by induction
= x:xs
2
6.8 Summary
A list is a sequence of values, its elements, all of the same type. Lists
are widely used in functional languages and are provided as a built-in
type in Miranda in order to provide some convenient syntax for their
use, for example, !] (the empty list), !1,3,5,7].
If xs is a list whose elements are of type *, then xs is of type !*].
The append operator ++ on lists puts two lists together. For example,
!1,2,3,4]++!5,6,7,8] = !1,2,3,4,5,6,7,8]. It satises the laws
xs++!] = !]++xs = xs unit laws
xs++(ys++zs) = (xs++ys)++zs associativity
As a consequence of associativity, if you append together several lists,
you do not need any parentheses to show in which order the appends
are done.
As long as a list xs is not empty, then its rst element is called its
head, hd xs, and its other elements form its tail, tl xs (another list). If
Exercises 87
x is a value (of the right type) and xs a list, then x:xs = !x]++xs is a
new list, `cons of x and xs', whose head is x and whose tail is xs.
Some other operators on lists are # (length) and ! (for indexing).
Every list can be expressed in terms of !] and : in exactly one way.
Thus pattern matching can be performed on lists using !] and :. This
makes : particularly useful in implementations, though ++ is usually
more useful in specications.
The special form !a..b] denotes the list of numbers in increasing order
from a to b inclusive.
A list of characters (also called a string) can alternatively be denoted
by using double quotation marks.
For a recursively dened list function, the recursion variant is usually
the length of some list.
The principle of list induction says that to prove 8xs : !
]: P (xs), it
suces to prove
base case: P (!])
induction step: 8x :
: 8xs :
]: (P (xs) ! P (x : xs))
This only works for nite lists.
6.9 Exercises
1. How would the evaluator respond to the expressions !1]:!] and !]:!]?
2. How would you use # and ! to nd the last element of a list?
3. Explain whether or not the expression !8,'8'] is well-formed and if not
why not.
4. Describe the dierence between 'k' and "k".
5. Dene a function singleton which given any list returns a Boolean
indicating if the list has just one element or not. Write a function
has2items to test if a list has exactly two items or not. Do not use
guards or the built-in operator #.
6. Consider the following specication of the indexing function !:
||pre: 0 <= n < #xs
||post: (E)us,vs:!*]. (#us = n & xs = us++!x]++vs)
|| where x = xs!n
(This is not quite right | the built-in ! has a defensive specication.)
Write a recursive denition of this function, and prove that it satises
the specication.
A straightforward way of writing specications for list functions is often
to use the indexing function and discuss the elements of the list. For
instance, you could specify ++ by
88 Lists
||pre: none
||post: #zs = #xs+#ys
|| & (A)n:nat. ((0 <= n < #xs -> zs!n = xs!n)
|| & (#xs <= n < #xs+#ys -> zs!n = ys!(n-#xs)))
|| where zs = xs++ys
Although this is straightforward, it has one disadvantage: when we
appended the lists, we had to re-index their elements and it is not so
terribly obvious that we did the calculations correctly.
For this reason, the specications in this book avoid the `indexing'
approach for lists wherever possible, and this exercise shows that even
indexing can be specied using ++ and #.
7. Write a denition of the function count:
count :: * -> !*] -> num
||pre: none
||post: (count x ys) = number of occurrences of x in ys
For example (using strings), count `o' \quick brown fox" = 2.
The specication is only informal, but try to show informally that your
denition satises it.
8. Consider the function locate of type * -> !*] -> num, locate x
ys being the subscript in ys of the rst occurrence of the element x, or
#ys if x does not occur in ys. (In other words, it is the length of the
largest initial sublist of ys that does not contain an x.) For instance,
locate `w' \the quick brown" = 13
Specify locate with pre- and post-conditions, write a Miranda denition
for it, and prove that it satises its specication.
If a character c is in a string s, then you should have
s!(locate c s) = c
Check this for some values of s and c.
9. Use box notation to write the proof of Proposition 6.1.
10. Specify and write the following functions for strings.
(a) Use count to write a function table which produces a list of the
the numbers of times each of the letters in the lowercase alphabet
and space appear in a string:
table\a bad dog" = 2 1 0 2 0 0 1 0 0 0 : : : 0 2]
Use the function table on a string and the same string in enciphered
form. What is the the relation between the two tables?
If you have a table generated from a large sample of typical English
text how might you use this information to decipher an enciphered
string. Can you think of a better enciphering method?
11. Consider the following Miranda denition:
scrub :: * -> !*] -> !*]
scrub x !] = !]
scrub x(y:ys) = scrub x ys, if x=y
= y:(scrub x ys), otherwise
Now consider the following more formal specication for scrub :
||pre: none
||post: ~isin(x,s)
|| /\ (E)xs:!*] ((A)y:* (isin(y,xs) -> y=x)
|| /\ Merge(xs,s,ys))
|| where s=scrub x ys
90 Lists
Types
7.1 Tuples
Recall three properties of lists of type !*] (for some type *):
1. They can be as long as you like.
2. All their elements must be of the same type, *.
3. They can be written using square brackets, !-,-,...,-].
There is another way of treating sequences that relaxes (2) (you can include
elements of dierent types) at the cost of restricting (1) (the length becomes
a xed part of the type). They are written using parentheses and are called
tuples.
The simplest are the 2-tuples (length 2), or pairs. For instance, (1,9),
(9,1) and (6,6) are three pairs of numbers. Their type is (num, num), and
their elements are called components. A triple (3-tuple) of numbers, such as
(1,2,3), has a dierent type, namely (num, num, num).
Note that each of the types (num,(num, num)), ((num, num), num) and
(num, num, num) is distinct. The rst is a pair whose second component is
also a pair, the second is a pair whose rst component is a pair, and the third
is a triple. There is no concept of a one-tuple, so the use of parentheses for
grouping does not conict with their use in tuple formation. One advantage
of the use of tuples is that if, for example, one accidentally writes a pair
instead of a triple, then the strong typing discipline can pinpoint the error.
We can dene functions over tuples by using pattern matching. For example,
selector functions on pairs can be dened by:
fst :: (*, **) -> *
snd :: (*, **) -> **
fst (x,y) = x
snd (x,y) = y
91
92 Types
Both fst and snd are polymorphic functions they select the rst and
second components of any pair of values. Neither function works on any other
tuple-type. Selector functions for other kinds of tuples have to be dened
separately for each case.
The following is a function which takes and returns a tuple (the quotient
and remainder of one number by another):
quotrem :: (num, num) -> (num, num)
quotrem (x,y) = (x div y, x mod y)
(Note that the pre-condition ensures that there is no need to consider cases
where one argument is empty and the other is not.)
To unzip a list, you want in eect two results | the two unzipped parts.
So the actual (single) result can be these two paired together, for example,
unzip !(1,2),(3,4),(5,6)] = (!1,3,5],!2,4,6])
7.3 Currying
Now that you have seen pairs, it might occur to you that there are dierent
ways of supplying the arguments to a multi-argument function. One is the
way that you have seen repeatedly already, as in
cylinderV :: num -> num -> num
cylinderV h r = volume h (areaofcircle r)
Another is to pair up the arguments, into a single tuple argument, as in
cylinderV' :: (num, num) -> num
cylinderV' (h,r) = volume h (areaofcircle r)
You might think that the dierence is trivial, but for Miranda they are quite
dierent functions, with dierent types and dierent notation (the second must
have its parentheses and comma).
To understand the dierence properly, you must realize that the rst type,
num -> num -> num, is actually shorthand for num -> (num -> num) cylinderV
is really a function of one argument (h), and the result of applying it,
cylinderV h, is another function, of type num -> num. cylinderV h r is
another shorthand, this time for (cylinderV h) r, that is, the result of
applying the function cylinderV h to an argument r.
This simple device for enabling multi-argument functions to be dened
without the use of tuples is called currying (named in honour of the
mathematician Haskell Curry). Therefore, multi-argument functions such as
cylinderV are said to be curried functions. cylinderV is the curried version
of cylinderV'.
Partial application
One advantage of currying is that it allows a simpler syntax by reducing the
number of parentheses (and commas) needed when dening multi-argument
functions. But the most important advantage of currying is that a curried
function does not have to be applied to all of its arguments at once. Curried
Currying 95
functions can be partially applied yielding a function which requires fewer
arguments.
For example, the expression (cylinderV 7) is a perfectly well-formed
expression which is a partial application of the function cylinderV. This
expression is an anonymous function (that is, a function without a name)
which maps a number to another number. Once this expression is applied to
some argument, say r, then a number is returned which is the volume of a
cylinder of height 7 and base radius of r.
Partial application is extremely convenient since it enables the creation of
new functions which are specializations of existing functions. For example, if
we now require a function, volume cylinder100, which computes the volume
of a cylinder of height 100 when given the radius of the base, this function
can be dened in the usual way:
volume_cylinder100 :: num -> num
volume_cylinder100 radius = cylinderV 100 radius
However, the same function can be written more concisely as
volume_cylinder100 = cylinderV 100
or indeed we may not even dene it as a separate function but just use the
expression (cylinderV 100) in its place whenever needed.
Even more importantly, a partial application can also be used as an actual
parameter to another function. This will become clear when we discuss
higher-order functions in Chapter 8.
Order of association
For currying to work properly we require function application to `associate to
the left': for example, smaller x y means (smaller x) y not smaller (x y).
Also, in order to reduce the number of parentheses required in type
declarations the function type operator -> associates to the right. Thus num
-> num -> num means num -> (num -> num) and not (num -> num) -> num.
You should by now be well used to omitting these parentheses, but as always,
you should put them in any cases where you are in doubt.
? 2 ?
* *2
? * 2 ? * 2
Figure 7.1
These forms can be regarded as the analogue of currying for inx operators.
They are a minor syntactic convenience, since all the above functions can be
explicitly dened. Note that there is one exception which applies to the use of
the minus operator. (-x) is always interpreted by the evaluator as being an
application of unary minus operator. Should the programmer want a function
which subtracts x from numbers then a function must be dened explicitly.
More examples of such partial applications are given in Chapter 8, where
simple higher-order functions are discussed.
7.4 Types
As we have seen from Chapter 4, expressions and their subexpressions all
have types associated with them.
expression of type: num
3 * 4
Figure 7.2
There are basic or primitive types (num, bool and char) whose values are
built-into the evaluator. There are also compound types whose values are
Types 97
constructed from those of other types. For example,
tuples of types,
function types (that is, from one given type to another),
lists of a given type.
Each type has associated with it certain operations which are not meaningful
for other types. For example, one cannot sensibly add a number to a list or
concatenate two functions.
Strong typing
Functional languages are strongly typed, that is, every well-formed expression
can be assigned a type that can be deduced from its subexpressions alone.
Thus any expression which cannot be assigned a sensible type (that is, is not
well-formed) has no value and is regarded as illegal and is rejected by Miranda
before evaluation. Strong typing does not require the explicit type declaration
of functions. The types can be inferred automatically by the evaluator.
There are two stages of analysis when a program is submitted for evaluation:
rst the syntax analysis picks up `grammatical' errors such as !1,)2((]
and if there are no syntax errors then the type analysis checks that the
expressions have sensible types, picking up errors such as 9 ++ True. Before
evaluation, the program or expression must pass both stages. A large number
of programming errors are due to functions being applied to arguments of
the wrong type. Thus one advantage of strong typing is that type errors
can be trapped by the type checker prior to program execution. Strong
typing also helps in the design of clear and well-structured programs. There
are also advantages with respect to the eciency of the implementation of
the language. For example, because all expressions are strongly typed, the
operator + knows at run-time that both its arguments are numeric it need
not perform any run-time checks.
Type polymorphism
As we have already seen with a number of list functions, some functions have
very general argument or result types. For example,
id x = x
The function id maps every member of the source type to itself. Its type is
therefore * -> * for some suitable type *. But * suits every type since the
denition does not require any particular properties from the elements of *.
Such general types are said to be generic or polymorphic (many-formed) types
98 Types
and can be represented by type variables. In Miranda there is an alphabet of
type variables, written *, **, ***, etc., each of which stands for an arbitrary
type. Therefore, id can be declared as follows:
id :: * -> *
Like other kinds of variables, a type variable can be instantiated to dierent
types in dierent circumstances. The expression (id 8) is well-formed and has
type num because num can be substituted for * in the type of id. Similarly,
(id double) is well-formed and has type num -> num. Similarly, (id id) is
well-formed and has type * -> * because the type (* -> *) can be substituted
for *. Thus, again like other kinds of variables, type-variables are instantiated
consistently throughout a single function application. The following are some
more examples:
sillysix :: * -> num
sillysix x = 6
second :: * -> ** -> **
second x y = y
Notice that in a type expression all occurrences of the same type variable
(for example, **) refer to the same unknown type at every occurrence.
Type synonyms
Although it is a good idea to declare the type of all functions that we dene,
it is sometimes inconvenient, or at least uninformative, to spell out the types
in terms of basic types. For such cases type synonyms can be used to give
more meaningful names. For example,
name == !char]
parents == (name, name)
age == num
weight == num
date == (num, !char], num)
A type synonym declaration does not introduce a new type, it simply attaches
a name to a type expression. You can then use the synonym in place of the
type expression wherever you want to. The special symbol == is used in the
declaration of type synonyms this avoids confusion with a value denition.
Type synonyms can make type declaration of functions shorter and can help
in understanding what the function does. For example,
databaseLookup :: name -> database -> parents
Type synonyms can not be recursive. Every synonym must be expressible in
terms of existing types. In fact should the program contain type errors the
type error messages will be expressed in terms of the names of the existing
types and not the type synonyms.
Type synonyms can also be generic in that they can be parameterized by
type variables. For example, consider the following type synonym declaration:
binop * == * -> * -> *
Thus binop num can be used as shorthand for num -> num -> num, for example,
smaller, cylinderV :: binop num
100 Types
7.5 Enumerated types
We can dene some simple types by explicit enumeration of their values (that
is, explicitly naming every value). For example,
day ::= Mon | Tue | Wed | Thu | Fri | Sat | Sun
direction ::= North | South | East | West
switch ::= On | Off
bool ::= False | True ||predefined
Note that the names of these values all begin with upper case letters. This is
a rule of Miranda. Values of enumerated type are ordered by the position in
which they appear in the enumeration, for instance
Mon < Tue < :::
These are easily used with pattern matching. For instance, suppose a point
on the plane is given by two Cartesian coordinates
point == (num, num)
Trees
a data item here
nothing!
Figure 7.3
By `tree' here, we mean some branching framework within which data can be
stored. In its greatest generality, each node (branching point) can hold some
data and have branches hanging o it (computer trees grow down!) and each
branch will lead down to another node. Also, branches do not rejoin lower
down | you never get a node that is at the bottom of two dierent branches.
To refer to the tree as a whole you just refer to its top node, because all the
rest can be accessed by following the branches down.
We are going to look at a particularly simple kind in which there are only
two kinds of nodes:
a `tree' node has an item of data and two branches.
a `leaf' node has no data and no branches.
These will correspond to two constructors: the rst, Node, packages together
data and two trees and the second, Emptytree, packages together nothing:
104 Types
tree * ::= Emptytree | Node (tree *) * (tree *)
where * is the type of the data items.
Figure 7.4
Ordered trees are very useful as storage structures, storing data items (of type
*) as the `x' components of Nodes. This is because to check whether y is
stored in Node t1 x t2, you do not have to search the whole tree. If y = x
then you have already found it if y < x you only need to check t1 and if
y > x you check t2.
Hence lookup is very quick, but there is a price: when you insert a new
value, you must ensure that the updated tree is still ordered. The following is
a function to do this. Notice that we have fallen far short of a formal logical
account there is a lot of English. But we have at least given a reasoned
account of what we are trying to do and how we are doing it, so it can be
considered fairly rigorous:
Recursively dened types 105
insertT :: * -> (tree *) -> (tree *)
||pre: t is ordered
||post: insertT n t is ordered, and its node
|| values are those of t together with n.
The rst example illustrates the rst line only of the above table: it has
four constructors (without arguments) and four corresponding boxes.
direction ::= North | South | East | West
The boxes given above are a streamlined version setting out what is
needed to complete the proof (exercise | show how this works).
The second example moves on to the second line of the table, bringing
in constructors with arguments:
distance ::= Mile num | Km num | NautMile num
P (Emptylist) XS : diylist
P (XS )
...
P (Cons X XS )
8xs : diylist
:P (xs)
Again, this is just a familiar (list) induction translated into the
do-it-yourself notation.
Notice how because cons has two arguments, there are two new constants
X and XS in the proof box. But only its second argument is recursively
of type diylist *, so there is only one induction hypothesis, P (XS).
Finally, we come to tree induction:
tree * ::= Emptytree | Node (tree *) * (tree *)
... t1 : tree
P (t1)
P (Emptytree) x:
t2 : tree
P (t2)
...
P (Node t1 x t2)
8t : tree
:P (t)
Structural induction 109
This is an entirely new induction principle! It says that to prove
8t : tree
P (t)], it suces to prove
a base case, P (Emptytree)
an induction step, P (Node t1 x t2), assuming that P (t1) and P (t2 ) both
hold (two induction hypotheses).
(All this is subject to the usual proviso, that it only works for nite trees |
in Miranda, innite trees are just as easy to dene as innite lists.)
Is this induction principle really valid? As it happens, it is, and it is
justied in Exercise 25. But it is not so important to understand the
justication as the pattern of turning a datatype denition into an induction
principle.
The following is an application. (The specications are not given formally,
but you can give informal proofs that the denitions satisfy the informal
specications.)
flatten :: (tree *) -> !*]
||pre: none
||post: the elements of flatten t are exactly the node values of t
flatten Emptytree = !]
flatten (Node t1 x t2) = (flatten t1) ++ (x:(flatten t2))
(
flatten revtree Emptytree ) =flatten Emptytree
= !]
= reverse !]
= reverse (flatten Emptytree)
The pattern works for any datatype newtype that is dened using
constructors. The key points to remember are
There is a box for each constructor.
Within a box, there is a new constant introduced for each argument of
the corresponding constructor.
There is an induction hypothesis for each argument whose type is
newtype used recursively.
The property proved inductively is proved only for nite values of
newtype.
Base cases are those boxes with no induction hypotheses induction steps
are those with at least one induction hypothesis.
The method can be extended to mutually recursive types, each dened
using the others. Then you need separate properties for the dierent
types and you prove them all together, using induction hypotheses where
there is any kind of recursion.
We will describe the general principles, though to be honest you may see
these more clearly from the examples already given.
Each alternative in a type denition corresponds to a box in the proof, so
let us concentrate on one alternative:
thing ::= ... | A s1 ... sn | ...
A is a constructor, it has n arguments, and they are of types s1 : : : sn. Some
of these types may bething again, using recursion. They will give induction
hypotheses:
x1 : s1
...
xn : sn
P (xi)
P (xj )
...
P (A x1 xn)
8x : thing: P (x)
Summary 111
Recursion variants
Whenever a type newtype is dened using constructors, there is a natural
format for recursively dened functions on newtype, using pattern matching:
for each constructor you have a separate case with a pattern to extract the
arguments of the constructor, and the arguments of type newtype will be used
as arguments for the recursive calls of the function.
As long as you keep to this format, and also as long as you restrict yourself
to nite elements of newtype, the `circular reasoning' will be valid and you
will not need to dene a recursion variant.
What is happening in eect is that the argument of type newtype is itself
being used as a recursion variant, `decreasing' to one of its components.
This can be justied by dening a numerical recursion variant of type
newtype -> num that counts the number of constructors used for values of
newtype. It can also be justied using the structural induction just described.
7.9 Summary
One way of combining types to form new ones is to form a tuple-type
(for example, a pair, or a triple or a quadruple). Tuple-values are formed
by using the constructor (,: : : ,).
Using tuples, functions can return more than one result by packaging
their results into a single tuple.
A pattern serves two purposes. Firstly it species the form that arguments
must take before the rule can be applied secondly it decomposes the
arguments and names their components.
Multi-argument functions (also called curried functions) are functions
which take more than one argument (as opposed to those functions
which operate on a single argument such as a tuple).
An advantage of currying is that a curried function does not have to
be applied to all of its arguments at once. Curried functions can be
partially applied, yielding a function which is of fewer arguments.
Every expression has a type associated with it and each type has
associated with it a set of operations which are meaningful for that type.
Functional languages are strongly-typed, that is, every well-formed
expression can be assigned a type that can be deduced from its
subexpressions alone. Any expression which cannot be assigned a sensible
type (that is, is not well-typed) has no value and is rejected before
evaluation.
Generic or polymorphic (many-formed) types are represented using type
variables *, **, *** etc., each of which stands for an arbitrary type.
112 Types
Within a given type expression, all occurrences of the same type variable
refer to the same unknown type.
You can dene a type by listing the alternative forms of its values
(separated by |). Each alternative form is a constructor (whose name
begins with a capital letter) applied to some number of arguments. It
represents `the arguments packaged together in a wrapper that is clearly
marked with the constructor's name'.
This method subsumes the ideas of enumerated types, union types and
recursively dened types (such as trees).
The type denition determines both a natural format for recursive
denitions of functions taking arguments from the type, and an induction
principle for proving properties of values of the type.
If you restrict yourself to using the `natural format of recursive denitions'
then you can use `circular reasoning' just as though you had a recursion
variant.
Miranda allows innite values of the new types. The methods here apply
only to the nite values.
7.10 Exercises
1. What are the types of +, ->, -, ++, #, !, >=, ,
= hd and tl ?
2. Prove by induction on xs1 that zip satises
8xs1 xs2 : !
]:8ys1 ys2 : !
]:(#xs1 = #ys1 ^ #xs2 = #ys2 !
zip (xs1++xs2) (ys1++ys2) = (zip xs1 ys1)++(zip xs2 ys2))
3. Prove by induction on xs that unzip satises its specication, namely
that
8xs : !
]:8ys : !
]:(#xs = #ys ! unzip(zip xs ys) = (xs ys))
4. (a) Explain why the expression zip (unzip ps) is not well-typed. Can
you make it well-typed by redening zip?
(b) Prove by induction on ps that
8ps : !(
)]:8xs : !
]8ys : !
]:
(unzip ps = (xs ys) ! zip xs ys = ps)
(Note: box proofs will help you, but you will need to use a little
extra thought to deal with the pattern matching.)
5. Let P be a property of elements of type *, and consider a function
separate P specied as follows. (How it is dened will depend on P .)
Exercises 113
separate_P :: !*] -> (!*], !*])
||pre: none
||post: (A)x:* ((isin(x,Ps) -> P(x))
|| & (isin(x,notPs) -> not P(x)))
|| & Merge(Ps,notPs,zs)
|| where (Ps,notPs) = separate_P zs
separate P is supposed to `demerge' the elements of zs into those
satisfying P and those not.
Prove that this specication species the result uniquely.
6. (a) Recall the function scrub of Exercise 10, Chapter 6. Show that
scrub satises the following specication:
scrub :: * -> !*] -> !*]
||pre: none
||post: (E)xs:!*] (xs, scrub x ys) = separate_P ys
|| where (given x) P(u) is the property u = x.
24. Show (you can use the method of `trees as recursion variants') that if t
is an ordered tree then flatten t is an ordered list.
Hence show that the following denition satises the specication for
sort (Chapter 6):
treesort :: !*] -> !*]
treesort xs = flatten (build xs)
25. Suppose P (t) is a property of trees, and consider the following sentences:
Q def
= 8t : (tree
):P (t)
R def
= 8n : nat:8t : (tree
):(treesize t = n ! P (t))
Remember, as always, that we are talking only about nite trees.
(a) Use a box proof to show that Q $ R.
(b) Suppose you have a proof by tree induction of Q. Show how
you can use its ingredients to create a proof by course of values
induction of R. (Use the specication of treesize.)
Chapter 8
Higher-order functions
Example
Integration (we mean denite integration) takes a function f and two limits,
a and b, and returns a number. One way of calculating the denite integral is
by cutting the domain of integration into equal-sized slices, and guessing the
average height of the function in each slice. For example, if the function is to
be integrated from 0 to 5 in 10 slices, the slices are: 0 to 0.5, 0.5 to 1, and
so on up to 4.5 to 5. The guessed height for each slice is simply the value
of the function in the centre of each slice, such as f (4:75) for the last slice
in the example above. This assumes that the slices are rectangular-shaped,
rather than whatever curved shape the function actually has.
The guessed area of a slice is then the width (0.5 each, in the example)
times the guessed average height. The nal answer is the sum of the areas
of all the slices. The type of a function integrate which calculates the area
under a curve could be declared as follows:
function == num -> num
integrate :: function -> num -> num -> num -> num
||args are <function> <start> <finish> <no. of slices>
||pre: nat(n) & n>0
||post: (integrate f start finish n) is an estimate of the
|| integral of f from start to finish
This function is higher-order because it takes a function as one of its
arguments. The following is a denition of integrate:
integrate f start finish n
= sum (map area !1..n])
where
width = (finish - start) / n
area i = width * f(start + width * (i-0.5))
120 Higher-order functions
8.3 The higher-order function fold
Let us note straight away that the specication species fold uniquely. In
other words, if f 1 and f 2 both satisfy the specication, $f is associative, e is
an identity for $f and xs is a (nite!) list, then
f 1 f e xs = f 2 f e xs
This is easily proved by induction on xs, the induction step coming from
f 1 f e (x : xs) = (f 1 f e !x]) $f (f 1 f e xs) = x $f (f 1 f e xs)
8.4 Applications
We shall implement fold later. For the moment, let us look at some
applications. sum can be dened as fold (+) 0. (Notice how a built-in
inx operator can be passed as an argument to a higher-order function by
placing it in parentheses.) Once you have checked that + is associative (that
is, x+(y+z) = (x+y)+z) and 0 is an identity (x+0 = x = 0+x), then you know
immediately that sum (xs++ys) = (sum xs)+(sum ys). You do not need to
prove it by induction the induction will be done once and for all when we
implement fold and show that the specication is satised.
122 Higher-order functions
The analogous function product can be dened as fold (*) 1. Note that
subtraction and division are not associative, and it is less obvious what one
would mean by `the elements of a list folded together by subtraction'. The
function concat is dened as fold (++) !]. It takes a list of lists and
appends (or concatenates) them all together.
By combining fold and map, quite a wide range of functions can be dened.
For instance, count of Exercise 7 in Chapter 6 can be dened by
count x xs = fold (+) 0 (map f xs)
where f y = 1, if y = x
= 0. otherwise
Then we can prove the properties of count without using induction. For
instance,
count x (xs++ys) = fold (+) 0 (map f (xs++ys))
= fold (+) 0 (map f xs)++(map f ys)
= fold (+) 0 (map f xs)+fold (+) 0 (map f ys)
= (count x xs)+(count x ys)
There are two common implementations of fold. They have dierent names,
foldr and foldl, and this is because they can also be used when $f and
e do not satisfy the pre-conditions of fold, but they give dierent answers
| actually, they correspond to dierent bracketings. (In fact, they even have
more general types than fold, as you can see if you ask the Miranda system
what it thinks their types are.) foldr and foldl are rather dierent. We
shall show foldr here | it uses the same idea as sum | and leave the
discussion of foldl to Exercise 6. foldr f e xs calculates
(xs!0 $f : : : $f (xs!(#xs ; 1) $f e) : : :)
foldr f e !] = e
foldr f e (x:xs) = x $f (foldr f e xs)
8.6 Summary
Most list-processing functions can be described using higher-order
functions such as map and fold (which capture the two most common
patterns of recursion over lists). The same approach can also be applied
to other patterns of recursion and for user-dened types.
A small suite of higher-order functions to iterate over each data type
can be used to avoid writing many explicit recursive functions on that
124 Higher-order functions
type. Then an appropriately parameterized higher-order function is used
to dene the required function.
The technique can be compared with polymorphism where structures
(including functions, of course) of similar shape are described by a single
polymorphic denition. Higher-order functions are used to describe other
recursive functions with the same overall structure.
The functional programming `style' is to use higher-order functions since
they lead to concise and abstract programs.
It is usually easier to understand programs that avoid excessive use of
explicit recursion and to use library and higher-order functions whenever
possible.
Induction proofs can be done once and for all on the higher-order
functions.
8.7 Exercises
1. Prove that integrate terminates, assuming that the supplied function
terminates.
2. Dene a function sigma, which, given a function, say f, and two integers
corresponding to the lower and the upper limits of a range of integers,
say n and m, will capture the common mathematical notation of
X
m
fx
x=n
3. In the imperative programming language C there is a library function
called ctoi which converts a string to an integer. For example, ctoi
"123" gives 123. Declare and dene ctoi in Miranda. Ensure that your
denition is not recursive.
4. Give type declarations and denitions of functions curry and uncurry,
for example, uncurry f (x,y) = f x y.
5. This question is about writing a function to sort lists using what is
called a merging algorithm:
(a) Recall smerge, which, given two sorted lists, merged them into a
single sorted list. Show that smerge is associative and !] is an
identity for it.
(b) Write a function mergesort which sorts a list by converting it to a
list of singletons and then applying fold smerge !].
6. The other implementation of fold is foldl, which calculates
(: : : (e $f xs!0) $f : : : $f xs!(#xs ; 1))
foldl f a !] = a
foldl f a (x:xs) = foldl f (a $f x) xs
Exercises 125
Note that we have replaced e by a. This is because the parameter
is passed through the recursive calls of foldl, so even if it starts
o as an identity for $f it will not remain as $f's identity. In
general, still assuming that $f is associative and e is an identity for
it, foldl f a xs = a $f (foldl f e xs). This can be proved easily
by induction on xs but since we would still need another induction to
prove the equations of the specication, it is possible to combine both
induction proofs.
(a) Use induction on xs to prove that
8a :
: 8xs ys : !
]: foldl f a (xs++ys)
= foldl f (a $f (foldl f e xs)) ys
(Hint: In the induction step you use the induction hypothesis twice,
with dierent values substituted for a and ys. The unexpected one
has ys = !]. To avoid confusion, introduce new constants for your
8-introductions.)
(b) Deduce from (a) that
8a :
: 8xs : !
]: foldl f a xs = a $f (foldl f e xs)
(c) Deduce from (a) and (b) that
8xs ys : !
]: foldl f e (xs++ys)
= (foldl f e xs) $f (foldl f e ys)
and hence that foldl implements the specication for fold.
(d) Deduce that
foldr f e xs = foldl f e xs
The principles here are exactly the same as in Miranda, with three minor
points of dierence. First, the typing information, that is,
PROCEDURE CardMin(x,y: CARDINAL):CARDINAL
is compulsory in Modula-2. Second, comments look dierent: they are between
(* and *), instead of being after ||. Third, we are using the word result
in post-conditions to mean the value returned by the procedure. This means
that it would be inadvisable to have a variable called result because of the
confusion that would arise.
result has a special meaning in post-conditions of functions:
it means the value returned.
129
130 Specication for Modula-2 programs
Variables changing
What is not apparent from this example is that there is a big dierence
between Miranda and Modula-2: Modula-2 has variables that change their
values. Therefore, our reasoning must be able to cope with symbols that take
dierent values at dierent times. In general, because a variable may change
its value many times during the computation, there may be lots of dierent
times at which we may wish to put our nger on the value and talk about it.
There is a general technique for doing this. But in a procedure specication,
there are really only two values to talk about, before (on entry to) and after
(on return from) the procedure, and we use a special-purpose notation to
distinguish these.
A pre-condition must only talk about the values before the procedure is
executed, so when a variable is used in a pre-condition it means the value
before. A post-condition will usually want to compare the values before and
after, and this is where the special notation comes in. A variable with a
zero (for example, x0 or x 0) means the value before an unadorned variable
(for example, x or x) means the value after. We shall be consistent in using
unadorned variables to denote the value now (in the pre-condition, `now' is
the time of entry in the post-condition it is the time of return), and in using
various adornments such as the zero to show the value at some other time.
The following are two examples:
PROCEDURE Swap (VAR x,y: INTEGER)
(*pre: none
*post: x=y_0 & y=x_0
*)
PROCEDURE Sqrt (VAR x: REAL)
(* Replaces x by an approximation to its square root.
* epsilon is a global variable.
* pre: x>=0 & epsilon>0
* post: x>=0 & | x^2-x_0|< epsilon & epsilon = epsilon_0
*)
9.2 Mid-conditions
When we implement the specications, there is a very simple technique
for reasoning. It generalizes the idea of pre- and post-conditions by using
logical assertions that are supposed to hold at points in the middle of the
computation, not just at the beginning or end. We call them mid-conditions.
They are written as comments in the middle of the code.
The following is an implementation of Swap, with a complete set of
mid-conditions:
PROCEDURE Swap (VAR x,y: INTEGER)
(* pre: none
*post: x=y_0 & y=x_0
*)
VAR z: INTEGER
BEGIN (*x=x_0 & y=y_0*)
z:=x (*z=x_0 & y=y_0*)
x:=y (*z=x_0 & x=y_0*)
y:=z (*y=x_0 & x=y_0*)
END Swap
You would not normally put in so many mid-conditions. There are just
certain key positions where they are important | you have already seen two,
namely entry and return (corresponding to pre- and post-conditions). With
most simple straight-line sections of code such as this it is easy to omit the
intermediate mid-conditions and ll them in mentally. But we can use the
example to illustrate the reasoning involved.
Each mid-condition is supposed to hold whenever program control passes
through that point | at least, provided that the procedure was called
correctly, with the pre-condition holding. (Note that unadorned variables still
denote the value `now', that is, at the time when control passes through that
point zeroed variables denote the value on entry.) Does this work here?
The rst mid-condition, x = x0 ^ y = y0, holds by denition: we have only
just entered the procedure, so the value of x has to be its value on entry,
which is x0 by denition.
Now look at the next mid-condition, z = x0 ^ y = y0. To have arrived here,
we must have started at the point where we had x = x0 ^ y = y0, and then
done the assignment z := x. It is not dicult to see that this is bound to
132 Specication for Modula-2 programs
set up the mid-condition we are looking at (though there are formal systems
in which this can be proved | in eect they dene the meaning of the
assignment statement).
The next mid-condition is similar, and nally we reach the nal mid-
condition, which is the post-condition. By this stage we know that by the
time the program returns it must have set up the post-condition.
Note the `stepping stone' nature of the reasoning. To justify a mid-condition
we do not look at all the computation that has gone before, but, rather, at
the preceding program statement and the mid-condition just before that.
Conditionals
Here is an example with an IF statement.
PROCEDURE IntMax (x,y: INTEGER):INTEGER
(*pre: none
*post: (result = x_0 \/ result = y_0) &
* (result >=x_0 & result >=y_0)
*)
BEGIN
IF x>=y
THEN (*x>=y*) RETURN x (* result = x_0 & result >=y_0*)
ELSE (*x<y*) RETURN y (* result = y_0 & result >x_0*)
END
END IntMax
There are two branches of the code, the THEN and ELSE parts, and in each
we can write a mid-condition based on the condition `IF x y'. For instance,
when we enter the THEN part, that can only be because the condition has
evaluated as TRUE: so we know at that point that x y. (This is relying on
the fact that there are no side-eects when the condition x y is evaluated.)
After RETURN x, we know that the result is x and also, because we knew
x y, that result y. The other branch, the ELSE part, is similar. On
entering it, we know that the condition evaluated as FALSE, so x < y.
Finally, we must show that the post-condition is set up. There are two
return points, each with a dierent mid-condition. But it is a matter of logic
(and properties of ) to show that
result = x ^ result y
! (result = x _ result = y ) ^ result x ^ result y
result = y ^ result > x
! (result = x _ result = y ) ^ result x ^ result y
Calling procedures 133
9.3 Calling procedures
When you specify a procedure, the zero convention is very convenient and
throughout that procedure you use the zeroed variables for the values on
entry. But when you call the procedure, you must be careful about the zeroes
in its specication: because you now have two contexts, the called procedure
and the calling context, in which zero has dierent meanings.
The following is an example of a rather simple sorting algorithm. The rst
procedure, Order2, sorts two variables, and the second, Order3, uses Order2
to sort three variables.
PROCEDURE Order2 (VAR x,y: INTEGER)
(*pre: none
*post: ((x=x_0 & y=y_0) \/ (x=y_0 & y=x_0)) & x<=y
*)
BEGIN (*x=x_0 & y=y_0*)
IF x>y
THEN (*x_0>y_0*) Swap(x,y) (*x=y_0 & y=x_0 & x<y*)
(* ELSE x_0<=y_0*) (*x=x_0 & y=y_0 & x<=y*)
END (*either way, x<=y*)
END Order2
Before giving the denition of Order3, let us outline the idea. We are ordering
x, y and z. If we can arrange for z to be the greatest, that is, x z ^ y z,
then the rest is easy: just order x and y. So this condition becomes a key
objective in our computation strategy, dividing the task into two. It appears
as the second mid-condition. You can probably believe that this objective is
achievable using Order2(y,z) and Order2(x,z), and we shall show this more
carefully.
On this analysis, the rst two mid-conditions are slightly dierent in
character. The second is a computational objective, used to specify the task
of the rst part of the code. As a condition it does not express everything
known at that point, but, rather, just something achievable that gives us
what we need to be able to nish o the problem. The rst mid-condition,
on the other hand, is more to help us reason that our code, once written,
really does work:
PROCEDURE Order3 (VAR x,y,z: INTEGER)
(*pre: none
*post: x,y,z are a permutation of x_0,y_0,z_0 & x<=y<=z
*)
BEGIN
Order2(y,z) (*y<=z*)
Order2(x,z) (*y<=z & x<=z*)
Order2(x,y) (*x<=y<=z*)
END Order3
134 Specication for Modula-2 programs
How do we know that Order3 works? (Are you actually convinced at this
stage?) Let us dispose straight away of the specication that x y and z are a
permutation of x0, y0 and z0 (that is, the same values, possibly rearranged).
Although it is actually quite dicult to express this in pure logic, it is quite
clear that each call of Order2 just permutes the variables, so that is all the
three consecutive calls can do. The real problem is knowing that the order is
correct in the end.
The rst call certainly sets up the rst mid-condition, y z, but how
do we know that the second call does not spoil this? We must look at
the specication of Order2, which says (after we have substituted the actual
parameter z for the formal parameter y)
((x = x0 ^ z = z0) _ (x = z0 ^ z = x0)) ^ x z
The zero here denotes the value on entry to the (second) call of Order2, but
we are reasoning about Order3, trying to prove it correct: so for us the zero
could also denote the value on entry to Order3. To avoid the conict, you
have to invent some new names: say x1, y1 and z1 for the values of x y and
z between the rst two Order2s: At that point we have, by the mid-condition,
y1 z1, and this is eternally true | because y1 and z1 are unchanging values
not computer variables.
Now what Order2 sees as x0 and z0 are | in our Order3 context | x1 and
z1. Hence on return from the second Order2, we can use its post-condition to
write
((x = x1 ^ z = z1) _ (x = z1 ^ z = x1)) ^ x z ^ y = y1
So far, although we have said a lot by way of explanation, all that has
happened has been some notational manipulation and with practice you should
be able to do it automatically. What comes next is real logic Figure 9.1
contains a box proof that shows y z ^ x z.
More compactly, we want to show at this point that y z, that is, y1 z.
Since y1 z1, it is sucient to show that z1 z (that is, Order2(x, z) cannot
decrease the value of z you would expect this intuitively, but we can also
prove it). There are two cases. If (x = x1 ^ z = z1), that is, Order2 did not
do a swap, then z1 = z. In the other case, we have (x = z1 ^ z = x1 ^ x z),
so z1 = x z.
We have now proved that the second mid-condition, y z ^ x z, is set up
correctly. For the third mid-condition, the fact that z is greater than both x
and y is unaected whatever Order2(x y) does, while it also ensures x y.
Hence, nally, x y z.
9.5 Examples
The following procedure swaps the values of two variables without using any
extra variables as storage space. Mid-conditions show very clearly how the
sequence of assignments works:
PROCEDURE Swap (VAR x,y: INTEGER)
(* pre: none
*post: x=y_0 & y=x_0
*)
BEGIN (*x=x_0 & y=y_0*)
x:=x-y (*x=x_0-y_0 & y=y_0*)
y:=x+y (*x=x_0-y_0 & y=x_0*)
x:=y-x (*x=y_0 & y=x_0*)
END Swap
Examples 137
Walkies Square
Imagine a Walkies package with position coordinates X and Y , and
procedures Up and Right for updating these:
VAR X,Y: INTEGER
PROCEDURE Up(n: INTEGER)
(*pre: none
*post: X=X_0 & Y=Y_0+n
*)
PROCEDURE Right(n: INTEGER)
(*pre: none
*post: X=X_0+n & Y=Y_0
*)
We can use mid-conditions to show that the following procedure returns with
X and Y unchanged:
PROCEDURE Square(n: INTEGER)
(*pre: none
*post: ... & X=X_0 & Y=Y_0
*)
BEGIN (*X=X_0 & Y=Y_0*)
Right(n) (*X=X_0+n & Y=Y_0*)
Up(n) (*X=X_0+n & Y=Y_0+n*)
Right(-n) (*X=X_0 & Y=Y_0+n*)
Up(-n) (*X=X_0 & Y=Y_0*)
END Square
It is reasonably clear that these mid-conditions are correct. But to justify
this more formally you need to use the specications of Right and Up. For
instance, consider the call `Right(;n)'. In the specication for Right, X0 and
Y0 mean the values of X and Y on entry to Right, and not, as we should
like to use them in the mid-conditions, on entry to Square. But we do know
(from the preceding mid-condition) that on entry to this call of Right X and
Y have the values X0 + n and Y0 + n(where X0 and Y0 are values on entry to
Square), so we can substitute these into the post-condition for Right. Also,
Right is called with actual parameter ;n, so we must substitute this for the
formal parameter n in the post-condition. All in all, in X = X0 + n & Y = Y0
substitute
;n for n,
X0 + n for X0 ,
Y0 + n for Y0 ,
giving X = X0 & Y = Y0 + n. This is the next mid-condition.
138 Specication for Modula-2 programs
9.6 Calling procedures in general
A typical step of reasoning round a procedure call looks as follows:
: : : mid1(x y z : : :) P (a b c : : :) mid2(x y z : : :)
Here x y z : : : represent the relevant variables, and a b c : : : , expressions
involving the variables, are the actual parameters in the call of P . We assume
for simplicity that evaluating these actual parameters does not call functions
that cause any side-eects. We have reasoned that mid1 holds just before
entry to P (imagine freezing the computer and inspecting the variables: they
should satisfy the logical condition mid1, and we now want to reason that
mid2 will hold on return). We must do this by using the specication of P
however, that is written using the formal parameters of P , and the rst step
is to replace these by the actual parameters a b c : : : to obtain the properties
of x y z : : :
pre: preP(x,y,z,... )
post: postP(x,x_0,y,y_0,z,z_0,... )
9.8 Summary
For Modula-2 the essential ideas of pre- and post-conditions (also
recursion variants) are the same as for Miranda result in a post-condition
means the result of the procedure.
Variables change their values, so a logical condition must always carry
an idea of `now', a particular moment in the computation. For pre-
and post-conditions, `now' is, respectively, entry to and return from a
procedure.
An unadorned variable always denotes its value `now'.
A zero on a variable indicates its value `originally', that is, on entry
to the procedure it appears in.
Introduce new constant symbols (for example, variables adorned with 1s)
as necessary to indicate values at other times.
There are implicit post-conditions: variables not mentioned, and local
variables, are not changed.
Mid-conditions can be used as computational objectives (`post-conditions
for parts of a procedure body') and to help reason correctness.
In an IF statement, the test gives pre-conditions for the THEN and ELSE
parts.
140 Specication for Modula-2 programs
When reasoning about procedure calls, there are three parts:
1. notational manipulation to see what the pre- and post-conditions
say in the calling context
2. logical deduction to prove the pre-condition
3. logical deduction to prove the next mid-condition (what you wanted
to achieve by the procedure call).
9.9 Exercises
1. You have already seen the following problems for solution in Miranda:
round: round a real number to the nearest integer.
solve: solve the quadratic equation ax2 + bx + c = 0.
middle: nd the middle one of three numbers.
newtonsqrt: calculate a square root by Newton's method.
Translate the Miranda solutions (specications and denitions) directly
into Modula-2.
2. The following standard procedures are dened in Niklaus Wirth's
Programming in Modula-2: ABS, CAP, CHR, FLOAT, ODD, TRUNC, DEC, INC.
Try to translate the explanations in the report into formal, logical
specications.
3. Implement the middle function (see Exercise 1) in Modula-2 using the
SWAP procedure instead of recursion. Show that it works correctly.
4. Specify and dene Modula-2 procedures Order4 and Order5 analogous
to Order3, and using the same method, a straight-line sequence of calls
of Order2. Prove that they work correctly. Can you show that you use
the minimum number of calls of Order2? Is there a general argument
that shows that this method works for ordering any given number of
variables?
Chapter 10
Loops
2B + 2G
BB GG
BG
1B + 2G 1B + 2G 3B + 0G
Notice how the invariant, the parity, does not in itself tell us much about
the numbers of beans. It is only when we reach the end that the parity
combines with that fact to give very precise information about the numbers.
Another small point. How do we know that we ever reach a state with
only one bean? This is obvious, because the total number of beans always
decreases by one at each move. This total number is called a variant because
it varies and it works very like recursion variants.
10.3 Termination
If we nish looping, then we know the combination `invariant ^:loop test'
holds. But not all loops do terminate. Some loop for ever, and we want to
rule out this possibility. The Coee Tin Game must terminate, because each
move decreases by one the total number of beans left, but this can never go
negative. Therefore after nitely many moves, the game must stop.
In general, to reason with WHILE loops we use not only the invariant, a
logical condition as above, but also a loop variant. This works the same way
as does a recursion variant. It is a natural number related to the computer
variables such that the loop body must strictly decrease it, but it can never
go negative. Then only nitely many iterations are possible, so the WHILE
loop must eventually terminate.
For the Coee Tin, the variant is the total number of beans left.
10.4 An example
Apparently, the method of invariants and variants as presented so far is a
reasoning tool: given a WHILE loop, you might be able to nd a loop invariant
to prove that it works. But actually, the invariant can appear much earlier
than that, even before you have written any code, as a clarication of how
you think the implementation will work. Let us explore this in a simple
problem to sum the elements of an array of reals:
146 Loops
PROCEDURE AddUp(A: ARRAY OF REAL):REAL
(*pre: none
*post: result = Sum (i=0 to HIGH(A))A!i]
*)
that is,
HIGH(A)
X
result = Ai]:
i=0
There is an obvious technique for doing this we read through the elements of
A with a variable subscript n and add them one by one into an accumulator
S.
Now imagine freezing the computation at the point when we have read
exactly n elements and added them all into S . Diagrammatically, the state of
the computer can be seen in Figure 10.3
n elements read S = sum of them
A: ...
0 n HIGH(A) + 1 ^ S = Ai]
i=0
This is the loop invariant. It also guides our programming:
P1
Initially (no elements read) we want n = 0 and S = 0 ( i=0 ;
Ai], the
empty sum).
An example 147
If n = HIGH(A) + 1, then S is the result we want and we can just return
it.
If n HIGH(A) then we want to read An], add it to S , and increment
n.
Thus the very act of formulating the invariant has subdivided our original
problem into three smaller ones: initialization, nalization, and reestablishing
the invariant. This is a very important aspect of the method.
And the variant? A natural number that decreases each time is the number
of elements left to be read: this is HIGH(A) + 1 ; n.
In eect we have now proved that the algorithm works, but we have not
written the program yet! For the sake of our idiot computer, we must
implement the algorithm in Modula-2:
PROCEDURE AddUp (A: ARRAY OF REAL):REAL
(*pre: none
*post: result = Sum (i=0 to HIGH(A))A!i]
*)
VAR n: CARDINAL
S: REAL
BEGIN
S:=0.0
n:=0.0
(* Loop invariant:
*0<=n<= HIGH(A)+1 & S= Sum(i=0 to n-1) A!i]
*Variant = HIGH(A)+1-n
*)
WHILE n<= HIGH(A) DO
S:=S+A!n]
n:=n+1
END
RETURN S
END AddUp
This is exactly the quantity of comments you should use in practice: the
specication and the invariant and variant. Once you have actually written
down the invariant, it is relatively easy | for you or for anyone else who
needs to look at your code | to check the minor details. For instance,
10.7 Summary
The method of loop invariants is the method of mid-conditions applied
to WHILE loops.
The invariant is a mid-condition that should always be true immediately
before the loop test is evaluated.
Do not confuse the loop invariant with the loop test. They are both
logical conditions, but
1. the loop invariant is a mid-condition, used in reasoning, not
evaluated by the computer, and intended to be true right through
to the end
2. the loop test is a Boolean expression, evaluated by the computer,
and is bound to be false after the last iteration.
The invariant arises rst (in your reasoned programming) as a
computational objective, often after drawing a diagram when the
reasoned program is completed, the invariant is used to give a correctness
proof.
The invariant is used to divide the overall problem into three:
initialization, loop body, and nalization.
The loop variant, a number, is like a recursion variant and is used to
prove termination.
FOR loops are best reserved for simpler problems in which the iterations
are independent of each other.
10.8 Exercises
1. The problem is to implement the following specication:
PROCEDURE Negs(A: ARRAY OF INTEGER):CARDINAL
(*pre: none
*post: no. of subscripts for which A!i]<0
*)
The idea is to inspect the elements starting at A0] and working up to
HIGH(A):
(a) Draw a diagram to illustrate the array when n elements have
been inspected | make it clear what are the subscripts of the
152 Loops
last element to have been inspected and the next element to be
inspected.
(b) What values will n take as the program proceeds?
(c) Write down the implementation (Modula-2 code), including the loop
invariant and variant as comments in the usual way. The invariant
should in eect translate the diagram of (a) into mathematical form.
(d) Use the invariant and the failure of the loop test to show that the
post-condition is set up.
(e) Show that whenever an array element is accessed, the subscript is
within bounds.
Note: (c) contains the ingredients that you should write down in
your practical programming.
2. Develop reasoned Modula-2 programs along the lines of Exercise 1 to
solve the following problems about arrays:
(a) Find the minimum element in an array of integers.
(b) Find whether an array of integers is in ascending order.
(c) Find the length of an array of CHARs, on the understanding that
if it contains the character NUL (assumed predened as a constant),
then that and any characters after it are not to be counted. (In
other words, NUL is understood as a terminator.)
(d) Find the median of an array of reals, that is, the array value closest
to the middle in the sense that as many array elements are smaller
than it as are greater than it. Is the problem any easier if the
array is known to be sorted?
3. Develop the procedure Search:
PROCEDURE Search(A: ARRAY OF INTEGER x:INTEGER):CARDINAL
(*pre: Sorted(A)
*post: result <= HIGH(A)+1
&(A)i:CARDINAL
((i< result ->A!i]<x)
&(result <=i<= HIGH(A)->A!i]>=x))*)
Using the pre- and post-conditions of Search (not the code), prove that
your implementation of IsIn works correctly. What this means is that
in every place where a result is returned, you must show that it is the
correct result.
5. Give FOR loop implementations of the following:
(a) IsIn
(b) Copy
PROCEDURE Copy(A: ARRAY OF INTEGER
VAR B: ARRAY OF INTEGER)
(* Copies A to B
*pre: HIGH(A) = HIGH(B)
*post: B=A
*)
Binary chop
How do you look up a word, `binary', say, in a dictionary? What you do not
do is to look through all the words in order, starting at page 1, until you
nd the word you want. If the dictionary had 1170 pages, you might have to
check all of them before you found your word (if it was `zymurgy'). Instead,
you open the dictionary about half way through, at `meridian', and you see
that `binary' must be in one of the pages in your left hand. You divide those
about half way through, at `drongo', and again you see that `binary' must
come before that. Each time, you halve the number of pages in which your
word might be:
Stage: 0 1 2 3 4 5 6 7 8 9 10 11
Pages left:1170 585 293 147 74 37 19 10 5 3 2 1
Hence, you have only to check eleven pages before you nd your word. This
method is called the `binary chop algorithm', and it relies crucially on the
fact that the entries in the dictionary are in alphabetical order. It is a very
important algorithm in computing contexts, and, what is more, it is a good
example of an algorithm that is very easy to get wrong if you try to write
the code without any preliminary thought.
There is another important lesson in this algorithm, namely that the natural
order of writing a procedure is not necessarily from top to bottom. (This is
similar to the way you write a natural deduction proof.) You know already
that the loop invariant should generally be worked out before the code here
the most important piece of code to be xed is the nalization part.
A:
We can translate this into logic. First, all elements with subscripts between 0
and result;1 inclusive are < x:
8i : nat (i < result ! Ai] < x) (11.1)
156 Binary chop
Second, all elements with subscripts between result and HIGH(A) inclusive are
x:
8i : nat (result i HIGH(A) ! Ai] x) (11.2)
Third, we should say what range the result will lie in. The extremes are
when all the elements of the array are x, when the result should be 0, and
when all the elements are < x, when the result should be HIGH(A) + 1 (notice
how in this case Aresult] is undened):
0 result HIGH(A) + 1 (11.3)
These three conditions will form the post-condition.
If x is present at all in the array, then we must have Aresult] = x.
(We shall prove that this holds a little later.) If x is absent, then either
Aresult] > x or result = HIGH(A) + 1.
A:
There are dierent ideas, for instance `when Right = Left+1 return Right'
which we could have chosen but we did not and, as it turns out, the method
we have chosen is simpler.
Next, let us formulate the invariant. We have a picture already, but we
also know that we are choosing <'s or 's precisely to make the Left and
Right parts of the invariant match parts 11.1 and 11.2 of the post-condition.
Therefore, it has to be
Left Right HIGH(A) + 1
^8i : nat: ((i < Left ! A!i] < x) ^ (Right i HIGH(A) ! A!i] x))
Let us also take the opportunity to say that the variant is Right ; Left.
We have already dealt with the nalization what next? The initialization
is easy | we want Left = 0 and Right = HIGH(A) + 1. All that remains is the
loop body.
The idea is to nd Middle between Left and Right and update either Left
or Right depending on the value of A!Middle]. How should we do that? Let
us be very careful to use the information precisely.
If A!Middle] < x, then Middle is in the `< x' area of the array so Left,
which is to be in the `?' area, can safely be set to Middle + 1. On the
other hand, if !Middle] x, then we must set Right to Middle (why not
Middle ; 1?). We have not said exactly what Middle is, but we have made a
start on the loop body:
158 Binary chop
Middle := ?
IF A!Middle] < x THEN
Left := Middle+1
ELSE
Right := Middle
END
It remains to assign a value to Middle, and it is important to see what
precisely are the requirements here | all we know so far is that Middle should
be (about) half way between Left and Right, or at least somewhere between
them. Consider how the invariant Left Right is reestablished. The new Left
may be Middle + 1, so we want Middle + 1 Right, that is, Middle < Right.
In the other case, the new Right is Middle, so we want Left Middle. We
can use a mid-condition to express these requirements as a computational
objective:
Middle := ? (* Left <= Middle < Right *)
It is not dicult to see that if we can achieve this, then the rest of the loop
body will reestablish the invariant and decrease the variant as well. We shall
see soon that we can assign (Left + Right) DIV 2, the rounded-down average
of Left and Right, to Middle. That is probably what you expected anyway,
but more care is needed here than you might think. In Exercise 1 you will
see a use of essentially the same algorithm in a dierent context where it is
more natural to require Left < Middle Right, and there it is necessary to
use ((Left + Right) DIV 2) + 1 | the problem comes when Right = Left + 1, so
that Middle = Left.
Figure 11.1
As an experiment, try to write the program code straight down from the
top without thinking of invariants. You will probably nd (everyone else does)
that it is not easy to get it right.
r:= Search(A,x)
how can we use A x and r to perform our check? Just to be sure, let us
write down what we know about r solely from the post-condition for Search:
r HIGH(A) + 1
^8i : nat: ((i < r ! A!i] < x) ^ (r i HIGH(A) ! A!i] x)) (
)
If A!r] = x, then x must be present while a quick look at one of the
diagrams above makes it fairly clear that if A!r] > x then x is absent. But
wait! Is A!r] dened? Not necessarily. r might be equal to HIGH(A) + 1 in
this case, x is absent because all the elements are < x.
Check that array subscripts are in bounds when you write the
program, not when you run it.
The code above relies on Modula-2's short circuit evaluation. That is,
A!r] = x will not be evaluated if r > HIGH(A). In other languages, such
as Pascal, Boolean expressions are evaluated completely even if the result is
known after the rst subexpression has been evaluated. The code after the
RETURN would then need to be written as the following:
IF r <= HIGH(A)
THEN RETURN A!r] = x
ELSE RETURN FALSE
END
Let us show as rigourously as possible that the code for IsIn satises its
specication: that if the returned Boolean value is TRUE then x is indeed
present in A (that is, 9i : nat: (i HIGH(A) ^ A!i] = x)), and that if FALSE
is returned then x is absent (that is, :9i : nat:(i HIGH(A) ^ A!i] = x)).
!rst case:]r > HIGH(A), so FALSE is returned. We know that r is a natural
number and that r HIGH(A) + 1, so r = HIGH(A) + 1. Then from
(
) 8i : nat:(i HIGH(A) ! A!i] < x), in other words all the elements of
A are < x | so x must be absent. Note that the invalid array access
A!r] is not attempted here because of the way in which Modula-2
evaluates AND.
!second case:]r HIGH(A) A!r] = x, so TRUE is returned. Certainly x is
present, with subscript r.
!third case:]r HIGH(A) A!r] 6= x, so FALSE is returned. Because
r HIGH(A) (
) tells us that A!r] x, so we must have A!r] > x.
Now consider any subscript i HIGH(A). If i < r, then (
) tells us
that A!i] < x, while if i r, then (using orderedness) A!i] A!r] > x.
Either way, A!i] 6= x, so :9i : nat: (i HIGH(A) ^ A!i] = x).
11.7 Summary
Binary chop is an important and ecient search algorithm if the elements
are arranged in order. You should know it.
The algorithm has many uses, but to use it eectively it is important to
understand exactly what the result represents (that is, to have a clear
162 Binary chop
specication).
There is a particular train of reasoning that leads to the algorithm
easily otherwise it is easy to get into a mess.
11.8 Exercises
1. What happens if you replace the assignment Left := Middle+1 in
Search by Left := Middle? (Hint: the invariant is still reestablished.)
A common belief is that the problem can be corrected by stopping early,
looping WHILE Left+1 < Right. Follow through this idea and see how it
gives more complicated code.
2. The following is another version of intsqrt by the binary search
algorithm:
intsqrt::num->num
||pre: x >= 0
||post: n = entier (sqrt x)
|| i.e. nat(n) & n^2 <= x & (n+1)^2 > x
|| where n = intsqrt x
intsqrt x = f x 0 (entier x)
where f x l r = l, if l = r
= ?, if m*m <= x
= ?, otherwise
where m = ?
||m satisfies some conditions
Specify f precisely and in full, and complete the denition. (Beware! m
is not (l + r)div 2, as you will see if you follow the method properly.)
3. Show that the specication of Search species the result uniquely. In
other words, if there are two natural numbers r and r that are both
0
Use this to deduce the following. Suppose that in A there is exactly one
index, i, for which Ai] = x. Then i = Search(A x).
4. There are other ways of giving the post-condition for Search. Here is
one that translates the informal specication much more directly:
post1: (result <= HIGH(A) & A!result]>=x
& (A)i:nat. (i<=HIGH(A) & A!i]>=x->i>=result))
\/(result=HIGH(A)+1
& (A)i:nat. (i<=HIGH(A)->A!i]<x))
Use natural deduction (together with standard properties of arithmetic)
to prove that
pre ` (post $ post1)
Exercises 163
where pre and post are the pre- and post-conditions for Search as
originally specied, and post1 is as given above.
Can you think of any other equivalent post-conditions?
5. This question examines how you might use Search to update an ordered
array.
First, a function Search1 is intended to work in the same way as
Search, but with a `soft HIGH' called High1 to allow for variable length
lists of integers within a xed length array. (We actually use a soft
version of HIGH(A) + 1. This allows us to specify an empty list by
setting High1 = 0.)
PROCEDURE Search1(A: ARRAY OF INTEGER
High1: CARDINAL x: INTEGER):CARDINAL
(*pre: High1<= HIGH(A)+1 & Sorted(A!0 to High1-1])
*post: result <= High1 & (A)i:nat.
* ((i< result ->A!i]<x)
* & (result <=i< High1->A!i]>=x))
*)
Quick sort
164
Quick sort | functional version 165
Idea: partition
It is so much easier to sort short lists than long ones that it helps to do a
preliminary crude sort, a partition with respect to some key k (Figure 12.1).
Figure 12.1
partition:: *->!*]->(!*],!*])
||pre: none
||post: Perm(xs,ys++zs)
|| all elements of ys are <=k
|| all elements of zs are >k
|| where (ys,zs) = (partition k xs)
Note that the specication does not uniquely determine the function. If
(ys zs) is a possible result, so is (ys',zs') where ys', zs' are any permutations
of ys and zs. It is simple to implement partition in Miranda, but we do
not need to | it is the specication that is important, and in the end we
will implement it by a totally imperative method. A pure functional quick
sort is not terribly quick and uses lots of space.
Figure 12.2
the ag gets scrambled (Figure 12.3), the stripes being cut up horizontally
and rearranged: It is desired to correct this in one pass, that is, inspecting
each stripelet once only. We are not told whether the three stripes are cut
into the same number of stripelets. The only permitted way of rearranging
stripelets is by swapping them, two at a time:
170 Quick sort
Figure 12.3
TYPE Colour = (red, white, blue)
Figure 12.4
At each iteration, we inspect the rst, that is, the top, grey (uninspected)
stripelet. If it is white, then it is already in the right place and we can move
on. If it is red, then we swap it with the rst white and move on. If it is
blue, then we swap it with the last grey before the blues but do not move on
because we have now fetched another grey to inspect. Finally, when there are
no greys left, then the stripelets are in the right order.
If we invent names for the pointers, then we can improve the diagram
(Figure 12.5). We have adopted a convention here: there are three boundaries
to be marked (red{white, white{grey and grey{blue), and the corresponding
variable is always the index of the element just after the boundary. If two
adjacent markers are equal, it shows that that region is empty. In particular,
when GreyStart=BlueStart then there are no greys left and the ag is in
order.
This diagram is essentially the loop invariant. At the appropriate points
in the computation, we can imagine freezing the computer, inspecting the
Dutch national ag 171
0
WhiteStart
GreyStart
BlueStart
high(A)+1
Figure 12.5
variables and the array, and asking whether the stripelets from, for instance
WhiteStart to GreyStart;1, are indeed all white, as the diagram suggests. In
other words, the diagram suggests a statement about the computer's state,
and our next task is to translate this into logic as the invariant. You will see
this in the implementation.
The variant, which is a measure of the amount of work left to be done, is
the size of the jumbled (grey) area: BlueStart;GreyStart. Progress is made
by reducing it:
PROCEDURE Restore(VAR A: ARRAY OF Colour)
VAR WhiteStart,GreyStart,BlueStart: CARDINAL
BEGIN
WhiteStart := 0 GreyStart := 0
BlueStart := HIGH(A)+1
(* loop invariant:
* Perm(A,A_0)
* & WhiteStart <= GreyStart <= BlueStart <= HIGH(A)+1
* & (A)i:nat. ((0 <= i < WhiteStart -> A!i] = red)
* & (WhiteStart <= i < GreyStart -> A!i] = white)
* & (BlueStart <= i <= HIGH(A) -> A!i] = blue))
* variant = BlueStart-GreyStart
*)
WHILE GreyStart < BlueStart DO
CASE A!GreyStart] OF
red: Swap(A!WhiteStart],A!GreyStart])
WhiteStart := WhiteStart+1
GreyStart := GreyStart+1
|white: GreyStart := GreyStart+1
|blue: Swap(A!GreyStart],A!BlueStart-1])
BlueStart := BlueStart-1
END
END
END Restore
172 Quick sort
Sample reasoning
Let us us look at just two examples of how to verify parts of the procedure.
First, why is Perm (A, A0) always true? This is because all we ever do to the
array is swap pairs of its elements, and a sequence of swaps is a permutation.
Next, why does the red part of the CASE statement reestablish the invariant?
Let us write WS, GS, BS and A1 for the values of WhiteStart, GreyStart,
BlueStart and A when the label red is reached.
We know that 0 WS GS < BS HIGH(A) + 1, so after the update,
when WhiteStart = WS + 1, GreyStart = GS + 1 and BlueStart = BS, we have
0 WhiteStart GreyStart BlueStart HIGH(A) + 1, as required. To check
that the colours are correct after the update, let i be a natural number.
If 0 i < WhiteStart, then 0 i WS . We must show that A!i] is red.
If i = WS, this follows from the specication of Swap:
A!WS] = A1!GS ] = red by the CASE switch.
If i < WS, which is GS, then i is neither WS nor GS. Hence A!i] was
unaected by the Swap, so A!i] = A1!i] = red by the loop invariant.
Next, suppose WhiteStart i < GreyStart, that is, WS + 1 i GS. Note in
this case that A1!WS] = white by the loop invariant, for WS < WS + 1 GS.
(The point is that the situation where WS = GS, and so A1!WS] is grey,
is impossible given the i that we are considering.) Hence for i = GS, the
specication of Swap tells us that A!GS] = A1!WS] = white.
If i < GS, then i is neither WS nor GS. Hence from the specication of
Swap, A!i] is unchanged, and by the loop invariant it was white. For the
third case, take BlueStart i HIGH(A). Again, A!i] is unchanged, and by
the loop invariant it was blue.
K grey
(white) crude sort
(midwives' ag)
K pink blue
(white) BlueStart
pink K blue
(white) BlueStart
Figure 12.6
the Partition from the Dutch national ag, we must:
1. Simplify Restore to do the Midwives' sort (drop the `red' case and
WhiteStart we can also turn the CASE statement to an IF statement).
2. Return the nal BlueStart as a result in order to show the boundary of
the partition.
3. Convert the colours to arithmetic inequalities ( or > the key K ).
4. Allow for partitioning regions, rather than the whole array.
There should be no need to reason that the implementation is correct because
we have done all the reasoning for Restore. But the loop invariant allows us
to check, in case of doubt:
12.7 Summary
Functional denitions can be useful reasoning tools even if the nal
implementation is to be imperative.
Sometimes a diagram is the real loop invariant.
The method of introducing logical constants to name the values of
computer variables is often (as in Restore) indispensable when you show
that the loop body reestablishes the invariant.
12.8 Exercises
1. For the Dutch national ag algorithm show the following:
(a) the invariant is established by the initialization
(b) the invariant is reestablished by each iteration (that is, do the blue
and white cases corresponding to the red case above)
(c) when looping stops, the post-condition has been set up
(d) the variant strictly decreases on each iteration, but never goes
negative
(e) for every array access or Swap, the subscripts are within bounds
(that is, HIGH(A)).
2. Consider the following idea for the Dutch national ag problem. The
Exercises 175
white stripelets are to be put at the other end of the grey area:
Red jGrey jWhite jBlue ]
" " "
GreyStart WhiteStart BlueStart
(a) Show that this is unsatisfactory for two reasons:
on average, more swaps are done than are necessary
this method can give wrong answers.
(b) Two other sequences of two swaps are possible is either of them
correct?
3. Can the Dutch national ag method be generalized to work with more
than three colours?
4. Implement partition in Miranda.
5. Modify the Miranda partition and qsort so that the order relation
used does not have to be , but is supplied as a parameter lte, a
`comparison function' which takes two elements as arguments and gives
a Boolean result:
partition1::(*->*-> bool)->* ->!*]->(!*],!*])
qsort1::(*->*-> bool)->!*]->!*]
Warshall's algorithm
Computer representation
The graph can also be thought of as a matrix, or array, and this is the
basis of the computer representation. If you give each node a number, then
the whereabouts of the edges can be described by a square array of Boolean
values: (
Edgea b] = true if there is an edge from a to b, that is, a ! b
false otherwise
This array, or matrix, is called the adjacency matrix of the graph. The
transitive closure can be described the same way:
(
Patha b] = true if there is a path from a to b, that is, a !+ b
false otherwise
Let us give some suitable declarations, and also specify the transitive closure
procedure:
178 Warshall's algorithm
CONST Size = ...(*number of nodes*)
TYPE
Node = 1..Size
AdjMatrix = ARRAY Node,Node OF BOOLEAN
You might decide to have Edge a VAR parameter, to avoid any possible
copying. Then you would need a pre-condition to say that Edge and Path
are dierent arrays, and an extra post-condition to say that Edge = Edge0.
Consider c0 : : : cn. There are at least Size + 1 of these symbols, but there
are only Size possible nodes. Therefore, one node appears twice ; ci = cj
where i < j . But this path can now be collapsed to a shorter path from a to
b:
a ! c1 ! : : : ! ci = cj ! : : : ! cn = b
(See Exercise 1 for a more rigorous induction proof.)
Detailed reasoning
initialization: This follows because a !1 b i Edgea b].
nalization: This follows because at the end N = Size, and a !+ b i a !r b
for some r Size, as reasoned above.
reestablishing the invariant: Let us split the invariant I1 into two parts:
I11 def 8a b : Node: (Patha b] ! (a !+ b))
I12 def 8a b : Node: (8r : nat: (a !r b) ^ 1 r N ! Patha b])
The rst thing to notice is that nothing ever spoils the truth of I11.
In particular, suppose it holds just before the assignment in the FOR
loop. The only possible change is if Pathi j ] becomes true because
we already have Pathi k] and Edgek j ] but then from I11 we know
(i !+ k) and (k ! j ), so (i !+ j ), as required, and I11 still holds
afterwards. Hence I11 holds right through the program.
Turning to I12, this involves N so we must take care to allow for the
increment N := N + 1. Let us write N1 for the old value of N after
the increment, N = N1 + 1. Before the FOR loops, I 2 told us that
if a !r b with r N1 then Patha b] and this much is never spoiled
because Patha b] never changes from true to false. Now suppose
afterwards that a !N1 +1 b, so there is a path of length N1 + 1 from a
to b. The last step of this path goes from c (say) to b, so we know
a !N1 c and Edgec b] by the previous invariant we know Patha c].
Now consider the FOR loop iteration when i = a, j = b and k = c:
because Patha c] = Edgec b] = true, this sets Patha b] to true and
it stays true for ever, as required.
This is a good example of the reasoning style for FOR loops that was suggested
in Section 10.6
180 Warshall's algorithm
Implementation
Detailed reasoning
initialization: This follows because a !0 b i a ! b.
nalization: Because a !Size b i a !+ b.
reestablishing the invariant: Let N1 be the value of N before the increment,
and let J be the following, which follows from the invariant I2:
8a b : Node: (Patha b] ! (a !+ b)) ^ ((a !N1 b ! Patha b]))
No iteration of the FOR loops ever spoils the truth of J so it is still true
after the FOR loops. However, the invariant will say something stronger
than J because of the increment of N , and we must check this.
Warshall's algorithm 183
Suppose a !N b, so there is a path from a to b with transit maximum
N (which is now N1 + 1). If all its transit nodes are actually N1 ,
then a !N1 b and so by J we know Patha b]. The only remaining case
is when some transit node is equal to N . Then by splitting up the
path we see that a !N1 N !N1 b, so by J we know that Patha N ]
and PathN b]. The FOR loop iteration when i = a and j = b makes
Patha b] equal to true and it remains so for ever.
Implementation
PROCEDURE TransClos(Edge: AdjMatrix VAR Path: AdjMatrix)
(*pre: none
*post: Path represents transitive closure of Edge
*notation: a-> n b means there is some path
* a-> c1-> c2-> ... -> cr -> b(r>=0)
* where c1, ... , cr are all <=n,i.e. its transit maximum is <=n.
* Hence a-> +b iff a-> Size b.
*)
VAR N: CARDINAL
i,j: Node
BEGIN
CopyAdjMatrix(Path,Edge)
N:=0
(*loop invariant I2:
* N<= Size
* & (A)a,b:Node.
* ((Path!a,b]-> (a-> +b))
* & ((a-> N b)-> Path!a,b]))
*variant = Size-N
*)
WHILE N < Size DO
N:=N+1
FOR i:=1 TO Size DO
FOR j:=1 TO Size DO
Path!i,j] := Path!i,j] OR (Path!i,N] AND Path!N,j])
END
END
END
END TransClos
184 Warshall's algorithm
Eciency
There are now three nested loops (for N i and j ), each one being executed
Size times, so the total number of iterations is of the order of Size3. This is
the best of our three algorithms.
We could optimize this further. For instance, we could replace the FOR
loops by
FOR i:=1 TO Size DO
IF Path!i,N]
THEN
FOR j:=1 TO N DO
Path!i,j] := Path!i,j] OR Path!N,j]
END
END
END
(Question: can you prove that this has the same result as the preceding
version?) However, this is local ne tuning. The step from the original version
to Warshall's was a fundamental change of algorithm, with a new Invariant.
13.4 Summary
We have given three algorithms to compute transitive closures, each one
fundamentally more ecient than the previous one.
The most ecient is Warshall's algorithm. It would be dicult to see
clearly why it works without the use of loop invariants.
The reasoning about FOR loops was essentially dierent from the loop
invariant technique used for WHILE loops.
13.5 Exercises
1. Given a graph with Size nodes, show that for any nodes a and b, if
a !+ b then a !r b for some r Size. Hint: use course of values
induction on n to show 8n : nat: P (n), where
3. Modify the detailed reasoning of the rst algorithm to justify the second.
4. Warshall's algorithm can be modied to compute shortest paths between
nodes in a graph. Here is the specication:
TYPE Matrix = ARRAY Node, Node OF CARDINAL
Tail recursion
reverse !] = !]
reverse (x:xs) = (reverse xs)++!x]
||reverse xs = rev1 !] xs
rev1 as !] = as
rev1 as (x:xs) = rev1 (x:as) xs
gcd x y = x, if y=0
= gcd y(x mod y), otherwise
fact n = 1, if n=0
= n*(fact (n-1)), otherwise
||fact n=f1 1 n
f1 a n = a, if n=0
= f1 (a*n) (n-1), otherwise
The following is the obvious recursive denition of the factorial function, but
it is not tail recursive:
fact :: num -> num
||pre: nat(n)
||post: fact n = n!
fact n = 1, if n=0
= n*(fact (n-1)), otherwise
Example: factorial 191
After the recursive call (fact(n;1)), there is still a residual computation
(n
: : :). However, these can be `accumulated' into a single variable:
f1 a n = a, if n=0
= f1(a*n)(n-1), otherwise
and then (fact n) = (f1 1 n) (but we shall have to prove this). a is the
accumulator parameter in f1. f1 is tail recursive, so you can convert it into
a WHILE loop. But in fact, we do not need to implement f1 separately in
Modula-2 we can put its WHILE loop into the implementation for fact, with
an extra local variable for the accumulator parameter:
PROCEDURE fact(n: CARDINAL):CARDINAL
(* NB may change n
*pre: none
*post: result = (fact n_0)
* where fact is as defined in Miranda
*)
VAR a: CARDINAL
BEGIN
a := 1
(*loop invariant: (fact n_0) = (f1 a n) where f1 as defined in Miranda
*variant = n
*)
WHILE n#0 DO a := a*n n := n-1 END
RETURN a
END fact
Justication
initialization: this relies on the property, promised but not yet proved, that
(fact n) = (f 1 1 n).
loop test and nalization: when n = 0, we know that (f 1 a n) is just a
but this is the answer we require, so we can just return a as the result.
reestablishing the invariant: when n 6= 0 then (f 1 a n) = (f 1 (a
n)(n ; 1)),
so we reestablish the invariant by replacing a and n by a
n and n ; 1.
It still remains to be shown that fact n = f 1 1 n. The method to use is
induction, but some care is needed. Suppose we try to use simple induction
on n to prove 8n : nat: P (n), where
P (n) fact n = f 1 1 n
192 Tail recursion
For the induction step we assume P (n), and prove P (n + 1) :
fact (n + 1) = (n + 1) (fact n) = (n + 1) (f 1 1 n)
= f 1 1 (n + 1) = f 1 (n + 1) n
How can we bridge the gap and prove (n + 1) (f 1 1 n) = f 1 (n + 1) n?
The answer is that we cannot. The inductive hypothesis only tells us about
the behaviour of f 1 when its accumulator parameter is 1. We actually have to
prove something more general and this involves understanding what (f 1 a n)
calculates for the general a: it is a n!, so we want to prove it equal to
a (fact n).
Proposition 14.1 : 8n : nat: fact n = f 1 1 n
Proof We rst prove by induction on n that 8n : nat: P (n) where
P (n) 8a : nat: a
(fact n) = f 1 a n
base case: f 1 a 0 = a = a
1 = a
(fact 0)
induction step: Assume P (n), and prove P (n + 1). Let a be a natural
number. Then
f 1 a (n + 1) = f 1(a
(n + 1))n
= a
(n + 1)
(fact n) by induction
= a
(fact (n + 1))
Hence 1
(fact n) = fact n = f 1 1 n:
2
For functions with accumulating parameters, you may need to rst understand
how the accumulator works, and then formulate a stronger statement to prove.
14.5 Summary
A recursive function is said to be tail recursive if in each recursive clause
of the denition the entire right-hand side of its equation consists of a
call to the function itself. A tail recursive function is similar to a loop.
A general technique for transforming recursive Miranda denitions into
WHILE loop Modula-2 denitions is as follows:
divmod::num->num->(num,num)
||pre: nat(m) & nat(n) & n >= 1
||post: divmod m n = (m div n, m mod n)
||i.e. nat(q) & nat(r) & r < n
|| & m = q*n + r
|| where (q,r) = divmod m n
divmod = f 0
where f a m n = (a,m), if m < n
= f (a+1) (m-n) n, otherwise
This is terribly inecient. Try fib 25. Why does it take so long?
A more ecient method is to calculate the pair (fib n fib (n + 1)):
194 Tail recursion
twofib :: num -> (num,num)
||pre: nat(n)
||post: twofib n = (fib n, fib(n+1))
twofib n=(0,1), if n=0
=(y,x+y), otherwise
where (x,y) = twofib (n-1)
fib1 n=x
where (x,y) = twofib n
Prove (by induction on n) that
8n : nat: twofib n = (fib n fib (n + 1))
5. Let us dene the generalized Fibonacci numbers (gfib x y n) by
gfib :: num -> num -> num -> num
||pre:nat(n)
||post: g fib x y n is the nth generalized Fibonacci number
|| (starting with "zeroth"=x, "first"=y)
gfib x y n=x, if n=0
=y, if n=1
=(gfib x y(n-2))+(gfib x y(n-1)), otherwise
They are generated by the same recurrence relation (the `otherwise'
alternative) as the ordinary Fibonaccis, but starting o with x and y
instead of 0 and 1.
(a) Prove by induction on n that
8n : nat: fib n = gfib 0 1 n
(b) Now the sequence (gfib y (x + y)) (as n varies) is the same as
the sequence (gfib x y) except that the rst term x is omitted:
(gfib x y (n + 1)) = (gfib y (x + y) n). Prove this by induction on
n.
Let us therefore dene
gfib1 x y n = x, if n=0
= gfib1 y(x+y)(n-1), otherwise
An introduction to logic
15.1 Logic
In this part of Reasoned Programming we investigate mathematical logic, which
provides the formal underpinnings for reasoning about programming and is
all about formalizing and justifying arguments. It uses the same rules of
deduction which we all use in drawing conclusions from premisses, that is, in
reasoning from assumptions to a conclusion. The rules used in this book are
deductive | if the premisses are believed to be true, then the conclusions
are bound to be true acceptance of the premisses forces acceptance of the
conclusion.
A program's specication can be used as the premiss for a logical argument
and various properties of the program may be deduced from it. These are
the conclusions about the program that we are forced to accept given that
the specication is true.
A :: num -> num
|| pre: none
||post: returns (x+1)^2
For example, in the program above, it can be deduced from the specication
of A that, for whatever argument (input) x that A is applied to, it delivers a
result 0. We cannot deduce, however, that it will always deliver a result
x2 unless the pre-condition is strengthened, for example to x 0.
Examples of applications of correct, or valid, reasoning are `I wrote both
program A and program B so I wrote program A', `if the machine is working
I run my programs the machine is working so I run my programs', `if my
programs are running the machine is working the machine is not working so
my programs are not running', etc.
It is not dicult to spot examples of the use of invalid reasoning political
debates are usually a good source. Some examples are `if wages increase
197
198 An introduction to logic
too fast then ination will get worse ination does get worse so wages are
increasing too fast', `some people manage to support their elderly relatives so
all people can'.
In this Chapter we introduce the language in which such deductions can be
expressed.
Atoms
An atom, or a proposition, is just a statement or a fact expressing that
a property holds for some individual or that a relationship holds between
several individuals, for example `Steve travels to work by train'. Sometimes,
the atoms are represented by single symbols such as Steve-goes-by-train . More
usually, the syntactic form is more complex. For instance, `Steve goes by
train' might be expressed as goesbytrain(Steve) or as travels(Steve, train).
The predicate symbol travels( , ) requires two arguments in order to become
an atom. Steve-goes-by-train (or SGT for short) is called a proposition symbol,
or a predicate symbol that needs no arguments. The predicate symbol
goesbytrain needs one argument to become an atom. The two arguments
of travels used here are Steve and train and the argument of goesbytrain
is Steve. Adjectives are translated into predicate symbols and nouns into
200 An introduction to logic
arguments, which is why, for example `programming is fun' is translated into
fun(programming) rather than into programming(fun).
You may come across the word arity, which is the number of arguments
a predicate symbol has. Predicate symbols with no arguments are called
propositional, predicate symbols of arity one express properties of individuals
and predicate symbols of arity two or more express relations between
individuals.
In English, predicates often involve several words which are distributed
around the nouns, or in front of or behind the nouns, but when translating,
a convention is used that puts the predicate symbol rst followed by the
arguments in parentheses and separated by commas. In case the predicate
has just two arguments it is sometimes written between the arguments in
inx form. Whenever a predicate symbol is introduced a description of the
property or relation it represents should be given. For example, travels(x y)
is read as `x travels by y'.
The arguments of predicate symbols are called terms. Terms can be simple
constants, names for particular individuals, but you can also build up more
complex ones using a structured or functional term which is a function symbol
with one or more arguments. For example, whereas an empty list may
be denoted by the constant ], a non-empty list is usually denoted by a
functional term of the form (head :tail), where head is the rst element and
tail is the list consisting of the rest of the elements. Thus the list cat,dog]
is represented by the term (cat : (dog : ])). Here : is an inx function
symbol.
An example of the use of a prex function symbol is s(0). Just as predicates
may have arities of any value 0, so can function symbols and each argument
of a functional term can also be a functional term. So functional terms can
be nested, as in mum(mum(Krysia)) or +(
(2 2) 3).
Suppose that Humphrey is over 21, that he has not been sentenced to
imprisonment before and that non-imprisonment is appropriate, then the
condition of the implication is false | although the rst conjunct is true the
second is not as each of the disjuncts is false. In this case, then, the whole
sentence is true, for an implication is true if its condition is false. You can
use this method for any other situation.
202 An introduction to logic
Some comments on the meanings of connectives
The truth tables give the connectives a meaning that is quite precise, more
precise in fact than that of their natural language counterparts, so care is
sometimes needed in translation.
The meaning of ^ is just like the meaning of `and' but notice that any
involvement of time is lost. Thus A and (then) B is simply A ^ B and,
for example, both `Krysia fell ill and had an operation' and `Krysia had
an operation and fell ill' are translated the same way. `A but B ' is also
translated as A ^ B , even though in general it implies that B is not usually
the case, as in `Krysia fell ill but carried on working'. To properly express
these sentences you need to use the quantier language of Section 15.4.
A _ B means `A or B or both'. The stronger, `A or B but not both',
can be captured by the sentence (A _ B ) ^ :(A ^ B ). The stronger meaning
is called exclusive or. For example, consider `donations to the cause will be
accepted in cash or by cheque' and `you can have either coee or tea after
dinner'. (Which of these is using the stronger, exclusive or?)
Consider the meaning of
diets(Jack) ! lose-weight(Jack)
that is,
`If Jack diets then he will lose weight'.
The only circumstance under which one can denitely say the statement is
false is when
Jack diets but stays fat.
In other circumstances, for example
Jack carries on eating, but gets thin
Jack carries on eating, and stays fat
there is no reason to doubt the original statement as the condition of that
statement is not true in these situations.
Natural language also uses other connectives, such as `only if' and `unless',
which can be translated using the connectives given already.
A unless B is usually translated as `A if :B ' (that is, :B ! A), in which
B occurs rather like an escape clause. A unless B can also can be translated
as B _ A. All of the sentences `Jack will not slim unless he diets', `either Jack
diets or he will not slim' and `Jack will not slim if he does not diet' can be
translated in the same way as diets(Jack) _: slims(Jack).
The quantier language 203
`A only if B ' is usually translated as A ! B , as in `you can enter only if
you have clean shoes', which would be `if you enter then you (must) have
clean shoes'. The temptation to translate A only if B as B ! A instead of
A ! B is very strong. To see the problem, consider
I shall go only if I am invited (A only if B )
Logically, it is A ! B | if you start from knowledge about A then you can
go on to deduce B (or that B must have happened). Temporally it is the
other way around | B (the invitation) comes rst and results in A. But A
is not inevitable (I might fall ill and be unable to go) so there is no logical
B ! A.
The sentence A $ B , is often dened as A ! B ^ B ! A, which is A only
if B and A if B , or A if and only if B , which is often shortened to A i B .
Pronouns
Pronouns, words such as `he', `it' or `nothing', do not in themselves refer to
any specic thing but gain their meaning from their context. You have already
seen how the words `something' and `everyone' are translated using quantiers
and `nothing' is similar | `nothing is striped' becomes :9x: striped(x).
Words such as `she' or `it' are used specically as a reference to someone or
something that has already been mentioned, so they inevitably correspond to
two or more references to the same value. When you come across a pronoun
such as this you must work out exactly what it does refer to. If that is a
constant then you replace it by the constant: so `Chris adores Pat who adores
her' becomes adores(Chris, Pat) ^ adores(Pat, Chris).
That is easy, but when the pronoun refers to a variable you must rst set
up a quantication and ensure that it applies to them both.
For instance, consider `something is spotted and it is hungry'. An erroneous
approach is to translate the two phrases `something is spotted' and `it is
hungry' separately. This is wrong because the `something' and `it' are linked
across the connective `and' and you must set up a variable to deal with this
linkage:
9x: x is spotted and x is hungry]
and then deal with the `and':
9x: spotted(x) ^ hungry(x)]
The rule of thumb is:
Some paradoxes
Generally, the need for a universal quantier is indicated by the presence
of such words as all, every, any, anyone, everything, etc., and the words
`someone', `something' indicate an existential quantier, but it can happen
that `someone' corresponds to 8. This phenomenon is most likely in connection
with !.
To see how this might happen, consider `if someone is tall then the door
frame will be knocked', which translates to
9x: tall(x)] ! door-knocked:
`Someone' has become 9 here, just as you would expect. But note that there
is an equivalent translation using 8. The original sentence could be rephrased
as `for anyone, if they are tall then the doorframe will be knocked', which
becomes
8x: tall(x) ! knocked(doorframe)]
Hence, in this example, `someone' can possibly become 8.
Now consider `if someone is tall then he will bump his head'. This time the
pronoun is linked to `someone' across the implication and you have to deal
with the quantication rst. The only translation is
8x: tall(x) ! bumphead(x)]
so that `someone' has to become 8.
15.8 Summary
Logic uses connectives to express the logical structure of natural language.
The syntax and meanings of propositional logic follow the principles of
algebra.
Exercises 211
Atoms consist of predicates which have arguments called terms. Terms
can be constants, or function symbols and their arguments.
For reference, the meanings can be summarized using a truth table. For
two propositions there are four dierent classes of situation: ftt ttg,
ftt ff g, fff ttg, fff ff g. Each row of the truth table gives one situation.
A B A^B A_B A ! B A $ B
A :A tt tt tt tt tt tt
tt ff tt ff ff tt ff ff
ff tt ff tt ff tt tt ff
ff ff ff ff tt tt
For example, from this truth table it can be seen that A _ B is true
unless both A and B are false.
To facilitate translation from English into logic, typed quantiers are
introduced.
The informal meaning of a sentence involving a quantier is
8x: P x] is true i every sentence P t] obtained by substituting t for
x, where t is taken from a suitable range of values, is true.
8x: P x] is false if some P t] is false.
9x: P x] is true i some sentence P t] obtained by substituting t for x,
where t is taken from a suitable range of values, is true.
9x: P x] is false if no sentence P t] is true.
Equivalent sentences can be substituted for one another.
15.9 Exercises
1. Suggest some predicate and function symbols to express the following
propositions: Mary enjoys sailing Bill enjoys hiking Mabel is John's
daughter Ann is a student and Ann is Mabel's daughter
2. Translate the following sentences into logic. First get the sentence
structure correct (where the ^, _, etc., go) and then structure the
atoms, for example Frank likes grapes could become likes(Frank, grapes).
(a) If there is a drought, standpipes will be needed.
(b) The house will be nished only if the outstanding bill is paid or if
the proprietor works on it himself.
(c) James will work hard and pass, or he belongs to the drama society.
(d) Frank bought grapes and either apples or pears.
(e) Janet likes cricket, but she likes baseball too.
(f) All out unless it snows!
212 An introduction to logic
3. Translate the following into logic as faithfully as possible:
(a) All red things are in the box.
(b) Only red things are in the box.
(c) No animal is both a cat and a dog.
(d) Anyone who admires himself admires someone.
(e) Every prize was won by a chimpanzee.
(f) One particular chimpanzee won all the prizes.
(g) Jack cannot run faster than anyone in the team.
(h) Jack cannot run faster than everyone in the team.
(i) A lecturer is content if she belongs to no committees.
(j) All rst year students have a programming tutor.
(k) No student has the same mathematics tutor and programming tutor.
(l) A number is a common multiple of two numbers if each divides it.
(m) Mary had a little lamb, its eece was white as snow. And
everywhere that Mary went her lamb was sure to go!
4. (a) Let A be tt, B be tt, C be ff . Which of the following sentences
are true and which are false?
i. ((A ! B ) ! :B )
ii. ((:A ! (:B ^ C )) _ B )
iii. ((((A _ :C ) ^ :B ) ! A) ! (:B ^ :C ))
(b) If A is ff , B is ff and C is tt, which of the sentences in part (a)
are true and which are false?
(c) If A is ff , B is tt and C is tt, which of the sentences in part (a)
are true and which are false?
5. We mentioned, but did not prove, that associativity allows you to omit
parentheses if all the connectives are _ or ^. Explain how associativity
is used to show the equivalence of ((Q _ R) _ S ) _ T and Q _ (R _ (S _ T )).
6. Show that the following are equivalent forms by considering all dierent
situations and showing that the pairs of sentences have the same truth
value in all of them. For example, for the equivalence P ^ ff ff there
are two situations to consider | P = tt and P = ff . When P = ff ,
P ^ ff = ff ^ ff = ff , and when P = tt, P ^ ff = tt ^ ff = ff . In both
cases the sentence is ff . For the example P ^ Q :(P ! :Q) there are
four situations to consider which can be tabulated as
P Q P ^ Q P ! :Q :(P ! :Q)
tt tt tt ff tt
tt ff ff tt ff
ff tt ff tt ff
ff ff ff tt ff
Exercises 213
You can see that the two sentences have the same value in all four
situations and so are equivalent.
(a) P _ Q (P ! Q) ! Q
(b) P ^ Q :(P ! :Q)
(c) P $ Q Q $ P (that is, $ is commutative)
(d) P $ (Q $ R) (P $ Q) $ R (that is, $ is associative)
(e) P $ Q :P $ :Q
(f) :(P $ Q) :P $ Q
(g) P ! (Q ! R) P ^ Q ! R
(h) P ! (Q ^ R) (P ! Q) ^ (P ! R)
7. Show that R S i R $ S is a tautology. (Hint: consider the possible
classes of situations for R $ S ).
8. Discuss how you would decide the truth or falsity of the sentences
below in the given situations. Also decide which are true in the given
situations and which are false (if feasible). The situation indicates the
possible values that can be substituted for the bound variables.
(a) All living creatures, animal or not:
i. 8x: animal(x) ! 9y: animal(y) ^ (eats(x y) _ eats(y x))]]
ii. 9u: animal(u) ^ 8v: animal(v) ! eats(v u)]]
iii. 8y: 8x: animal(x) ^ animal(y) ! (eats(x y) $ eats(y x))]
iv. :9v: animal(v) ^ 8u: animal(u) ! (:eats(u v))]]
(b) There are three creatures Cat, Bird and Worm. Cat eats all three,
Worm is eaten by all three and Bird only eats Worm. Use the
sentences (i) through (iv) of part (a) of this question.
(c) The universe of positive integers:
i. 9x: x is the product of two odd integers]
ii. 8x: x is the product of two odd integers]
iii. 8x: 9y: y > x]
iv. 8x: 8y: x y x]
9. By using the appropriate equivalences and translation of 8x : T: P x]
into 8x: is-T(x) ! P x]] and 9x : T: P x] into 9x: is-T(x) ^ P x]], show
that 8x : T: P x] ! S ] (9x : T: P x]) ! S .
10. Show that the following pairs of sentences are equivalent by using
equivalences. State the equivalences you use at each step:
(a) 8x: :8y: woman(y) ! :dislikes(x y)] ! dislikes(Jane x)]
and 8x: 9y: woman(y) ^ dislikes(x y)] ! dislikes(Jane x)]
(b) :9x: Martian(x) ^ :dislikes(x Mary) ^ age-more-than-25(x)]
and 8x: Martian(x) ^ age-more-than-25(x) ! dislikes(x Mary)]
Chapter 16
Natural deduction
16.1 Arguments
Now that you can express properties of your programs in logic we consider
how to reason with them to form correct proofs. Initially, we will look at
reasoning with sentences that do not include any quantiers.
The method we use is called natural deduction and it formalizes the
approach to reasoning embodied in the `argument form'
`This is so, that is so, so something else is so and hence something
else, and hence we have shown what we wanted to show.'
An argument leads from some statements, called the premisses, to a nal
statement, called the conclusion. It is valid if whenever circumstances make
the premisses true then they make the conclusion true as well. The only way
in which the conclusion of a valid argument can be rejected is by rejecting
the premisses (a useful way out).
We justify a potential argument by putting it together from small reasoning
steps that are all known to be valid. We write A ` B (pronounced `A proves
B ') to indicate that B can be derived from A using some correct rules of
reasoning. So, if we can nd a derivation, then A ` B is true.
Schematically:
P1 ` P2 fP1 P2g ` P3 fP1 P2 : : : Pn 1 g ` Pn :
;
The steps are supposed to be so simple that there is no doubting the validity
of each one.
The following is a valid argument:
1. If Hessam's program is less than 10 lines long then it is correct.
2. Hessam's program is not correct.
3. Therefore Hessam's program is more than 10 lines long.
The rst two lines are the premisses and the last the conclusion. A
derivation of the conclusion in this case is the following: suppose Hessam's
214
The natural deduction rules 215
program is less than 10 lines long then it is correct. But this contradicts the
second premiss so we conclude that Hessam's program is more than 10 lines
long. These reasoning steps mean that 1 2 ` 3.
Sometimes, we may be tempted to use invalid reasoning steps, in which the
conclusion does not always have to be the case even if the premisses are true.
Any justication involving such steps will not be correct.
The following is an invalid argument:
If I am wealthy then I give away lots of money.
I give away lots of money.
Therefore I am wealthy.
The reasoning is not valid because from the premisses you cannot derive the
conclusion the premisses could be true and yet I could be poor and generous.
If A ` B then the sentence A ! B is a tautology because whenever A is
true B must be true also. The various tautologies such as A ^ B ! A each
give rise to simple and valid arguments. This one yields the valid argument
A ^ B ` A.
An informal example
The natural deduction rules to be introduced in this chapter are quite formal.
This is a good thing for it enables a structure to be imposed on a proof
so that you can be condent it is valid. When you are quite sure of the
structure imposed by the rules it is possible to present proofs in a more
relaxed style using English. Typical of such an English proof is the following
proof of the valid argument:
If Chris is at home then he is working.
If Ann is at work then she is working.
Ann is at work or Chris is at home.
Therefore someone is working.
A justication of this argument might follow the steps: to show someone is
working, nd a person who is working | there are two cases to consider: if
Ann is at work, she is working and if Chris is at home, he is working. Either
way, someone is working.
.. ..
.
.
P1 Pn
P1 ^ : : : ^ Pn (^I )
The boxes are introduced to contain the proofs of P1 : : : Pn prior to
deriving P1 ^ : : : ^ Pn . The vertical dots indicate the proof that is to be lled
in. There is one box to contain the proof for each of P1 to Pn . The use of
the ^I rule is automatic | there is a standard plan which you always use
when proving P1 ^ : : : ^ Pn .
When a proof is presented, it is usually read from the top to the bottom,
but when you are actually proving something, you may work backwards from
the conclusion. So, in a proof, you will probably read an application of ^I
downwards, but when you have to prove P ^ Q, you ask `how do I do it?',
and the answer is by proving P and Q separately. We can say that you work
backwards from the conclusion, deriving a new conclusion to achieve.
The second rule is ^E :
from data or derived sentences P1 ^ : : : ^ Pn conclude any of
P1 : : : Pn , or
P1 ^ : : : ^ Pn
Pi (^E )
for each of Pi , i = 1 : : : n.
This time the rule is used exclusively in a forward direction, deriving new
data.
Figure 16.1 contains the rst steps in a proof of A ^ B ` B ^ A. If we
need to refer to lines in proofs then each row in the proof will be labelled
for reference. In the diagram, the given sentence A ^ B is initial data and is
placed at the start of the deduction, and the conclusion, or goal, is B ^ A,
which appears at the end. Our task is to ll in the middle.
There are now two ways to proceed | either forwards from the data or
backwards from the goal. In general, a natural deduction derivation involves
working in both directions. Here, as soon as you see the ^ in the conclusion,
218 Natural deduction
1 A^B
..
2 .
3 B^A
Figure 16.1
1 A^B
.. ..
2 . .
3 B A
4 B^A ^I
Figure 16.2
think (automatic step) ^I and prepare for it by making the preparation as in
Figure 16.2. Working backwards from the conclusion is generally applicable
when introduction rules are to be used. This example will require the use of
the ^I rule. The boxes are introduced to contain the subproofs of A and
B . It needs a tiny bit of ingenuity to notice that each of the subgoals can
now be derived by ^E from the initial data A ^ B by working forwards. The
completed proof appears in Figure 16.3. Lesson | the ^I step is automatic
1 A^B
2 B ^E (1) A ^E (1)
3 B^A ^I
Figure 16.3 A ^ B ` B ^ A
| to prove A ^ B you must prove A and B separately. But to use ^E
requires ingenuity | which conjunct should you choose?
An alternative proof construction for A ^ B ` B ^ A is shown in Figure 16.4.
It works forwards only | rst derive each of A and B from A ^ B and then
derive B ^ A.
You can see that these two rules are valid, from the denition of true
sentences of the form P ^ Q given in Chapter 15. For if P ^ Q is true then
so must each of P and Q be (^E ), and vice versa (^I ).
The natural deduction rules 219
1 A^B
2 A ^E (1)
3 B ^E (1)
4 B^A ^I
P1 ::: Pn
.. ..
P1 _ : : : _ Pn . .
C C
C (_E )
There is one box for each of Pi, i = 1 n.
Each box that is part of the preparation for the _E step represents a subproof
for one of the cases, and contains as an additional assumption the disjunct
Pi that represents its case. The assumptions Pi are only available inside the
box and their use corresponds to the English phrase `suppose that Pi : : : '.
Once the proof leaves the box we forget our supposition. Hence the box says
something signicant: Pi is true in here.
The _I rule is
_I From any one of P1 : : : Pn derive P1 _ : : : _ Pn
Pi
P1 _ : : : _ Pn (_I )
for each of Pi , i = 1 : : : n.
The _-introduction rule is usually used in a backward direction | in order to
show P _ Q one of P or Q must be shown. In the forward direction the rule
220 Natural deduction
is rather weak | if P is known then it does not seem very useful to derive
the weaker P _ Q (unless such a deduction is needed to obtain a particular
desired sentence, as in the next example). This rule, too, can be generalized
to n > 2 arguments.
This time, the _E rule is automatic, whereas the _I rule is the one that
requires ingenuity | when proving P1 _ : : : _ Pn which disjunct should we
choose to prove?
In the next example, a proof of A ^ (B _ C ) ` (A ^ B ) _ (A ^ C ), we illustrate
how a proof might be found. The rst step is to place the initial assumption
at the top and the conclusion at the bottom as in Figure 16.5. Now, where
A ^ (B _ C )
..
.
(A ^ B ) _ (A ^ C )
Figure 16.5
do we go from here? There are no automatic steps | ^E , and _I need
ingenuity. Can we obtain the conclusion by _I ? Does either of the sentences
A ^ B or A ^ C follow from the premiss? A little insight says no, so try ^E
on A ^ (B _ C ) | it is not so dicult and the result is given in Figure 16.6.
Now an automatic step is available | exploit B _ C by _E (case analysis).
A ^ (B _ C )
A ^E
B_C ^E
..
.
(A ^ B ) _ (A ^ C )
Figure 16.6
The preparation is given in Figure 16.7. Look at the left-hand box. There
are no automatic steps, but look, we can prove A ^ B by using B and then
use _I to show (A ^ B ) _ (A ^ C ). Similarly in the right-hand box, proving
A ^ C . The complete proof is given in Figure 16.8. It is often the case that a
disjunctive conclusion can be derived by exploiting a disjunction in the data.
Sometimes, an inspired guess can yield a result, as inside the boxes of the
example.
The natural deduction rules 221
A ^ (B _ C )
A ^E
B_C ^E
B C
.. ..
. .
(A ^ B ) _ (A ^ C ) (A ^ B ) _ (A ^ C )
(A ^ B ) _ (A ^ C ) _E
Figure 16.7
1 A ^ (B _ C )
2 A ^E (1)
3 B_C ^E (1)
4 B C
5 A^B ^I (2 4) A ^ C ^I (2 4)
6 (A ^ B ) _ (A ^ C ) _I (5) (A ^ B ) _ (A ^ C ) _I (5)
7 (A ^ B ) _ (A ^ C ) _E (3)
Figure 16.8 A ^ (B _ C ) ` (A ^ B ) _ (A ^ C )
As an example of how a box proof is translated into English, we will give
the same proof in its more usual form.
Proposition 16.1 A ^ (B _ C ) ` (A ^ B ) _ (A ^ C )
Proof Since A ^ (B _ C ), then A and B _ C . Consider B _ C : suppose B ,
then to show (A ^ B ) _ (A ^ C ) we have to show either A ^ B or A ^ B . In
this case we can show A ^ B . On the other hand, suppose C . In that case
we can show A ^ C and hence (A ^ B ) _ (A ^ C ). So in both cases we can
show (A ^ B ) _ (A ^ C ). 2
From now on you will have to work through the examples in order to see
how they are derived, as only the nal stage will usually be given.
It is easy to see that the _I rule is valid for X _ Y is true as long as
either X or Y is. If X _ Y is true then we know only that either X is true
or Y is true, but we cannot be sure which one is true. For the _E case,
therefore, we must be able to show C from both so as to be sure that C
must be true.
222 Natural deduction
It is tempting to try to ignore the _E rule because it looks complicated.
But you must learn it by heart! It is automatic | as soon as you see _ in a
premiss you should consider preparing for _E . Writing the conclusion in n + 1
places seems odd at rst, but this is what you must do. Each occurrence has
a dierent justication it is _E outside the boxes and other reasons inside.
There is a special case of _E in which the number of disjuncts is zero. A
disjunction of n sentences says `at least one of the disjuncts is true', but if
n = 0 that is impossible. To represent an impossible sentence, a contradiction,
we use the symbol ?, which is pronounced bottom and is always false. If you
look at _E when n = 0 you see that there are no cases to analyze and all
you are left with is
?
C (?E )
P P !Q
Q (!E )
It can be used both forwards from data and backwards from the conclusion.
To work backwards, suppose the conclusion is Q, then any data of the form
P ! Q can be used to derive Q if P can be derived. So P becomes a new
conclusion. In neither direction is the rule completely automatic | some
ingenuity is needed. The !E rule is commonly used in everyday arguments
and is also referred to as Modus Ponens.
The second rule is
!I from a proof of Q using the additional assumption P , derive
P ! Q.
P
..
.
Q
P !Q (!I )
The !I rule appears at rst sight to be less familiar. In common with
other introduction rules !I requires preparation | in this case, to derive
The natural deduction rules 223
P ! Q, a box is drawn to contain the assumption P and the subgoal Q
has to be derived in this box. The English form of P ! Q, `if P then Q',
indicates the proof technique exactly: if P holds then Q should follow, so
assume P and show that Q does follow. Note that the box shows exactly
where the temporary assumption is available. !I is an automatic rule and is
always used by working backwards from the conclusion.
The next example is to prove A ^ B ! C ` A ! (B ! C ). The rst
steps in this example are automatic. First, a preparation is made to prove
A ! (B ! C ), and then a second preparation is made to prove B ! C , both
by !I . These result in Figure 16.9. There are then two possibilities |
A^B !C
A
B
..
.
C
B!C !I
A ! (B ! C ) !I
Figure 16.9
you can either use A and B to give A ^ B and hence C , or you can use
A ^ B ! C to reduce the goal C to the goal A ^ B .
The nal proof is given in Figure 16.10. How might this proof appear in
1 A^B !C
2 A
3 B
4 A^B ^I (2 3)
5 C !E (1 4)
6 B!C !I
7 A ! (B ! C ) !I
Figure 16.10 A ^ B ! C ` A ! (B ! C )
224 Natural deduction
English?
Proposition 16.2 A ^ B ! C ` A ! (B ! C )
Proof To show A ! (B ! C ), assume A and show B ! C . To do this,
assume B and show C . Now, to show C , show A ^ B . But we can show
A ^ B since we have assumed both A and B . 2
The next three examples illustrate the use of the !E and !I rules. They
also use the useful X rule | if you want to prove A, and A is in the data,
then you can just `check' A.
A
A (X)
Show ` A ! A
There is only one real step in this example, and no initial data (Figure 16.11).
1 A
2 A X(1)
3 A!A !I
Figure 16.11 ` A ! A
Show A ` B ! A
1 A
2 B
3 A X(1)
4 B!A !I
Figure 16.12 A ` B ! A
Notice that the assumption B is not used inside the box (Figure 16.12).
Show P _ Q ` (P ! Q) ! Q
In Figure 16.13 the preparation for !I is made before that for _E . If
the preparation for using P _ Q were made before the preparation for the
conclusion, then the latter preparation would have to be made twice within
each of the boxes enforced by the preparation for _E .
The natural deduction rules 225
1 P _Q
2 P !Q
3 P Q
4 Q !E (2 3) Q X(3)
5 Q _E
6 (P ! Q) ! Q !I
Figure 16.13 P _ Q ` (P ! Q) ! Q
The validity of !E is easy to see, for the truth of P ! Q and P force Q
to be true by the denition of !. For the !I rule, remember that P ! Q
is true if P is false, or if P and Q are both true. So, in case P is true we
have to show Q as well.
1 :(A _ :A)
2 A
3 A _ :A _I (2)
4 ? :E (1 3)
5 :A :I
6 A _ :A _I (5)
7 ? :E (1 6)
8 ::(A _ :A) :I
9 A _ :A ::(8)
Figure 16.14 ` A _ :A
In Figure 16.14 the crucial step is to realize that A _ :A will follow from
::(A _ :A). Some ingenuity is again needed at lines 5 and 6 in deciding that
to prove A _ :A it is appropriate to show :A.
The :: rule is obviously valid. For :E , notice that a proof of P and
of :P gives P ^ :P , which is always false. For :I , we have to show that
P must be false | well, it must be if P leads to a contradiction, ?, for
otherwise ? would have to be true, which it cannot be.
Derived rules
1 :S
..
2 .
3 ?
4 ::S :I
5 S ::(4)
1 A_B
2 :A _ C
3 A B
4 :A C B_C _I
5 ? :E (3 4) B _ C _I
6 B_C ?E
7 B_C _E (2)
8 B_C _E (1)
Figure 16.15
The natural deduction rules 229
Some hints for deriving natural deduction proofs
You have put the assumptions at the top of a proof and the conclusion
at the bottom | what do you do next? You might be able to use some
automatic steps, !I for example, which yield a requirement for deriving
various subproofs. Or, you might be able to use some insight, for example to
prove C _ D using _I , prove C . Since introduction rules produce conclusions
they are usually used when lling in a proof from the bottom upwards |
their use is dictated by the form of the conclusion. Elimination rules work on
the data and so these are usually used when lling in a proof from the top
downwards.
In addition to these guidelines there are many useful tactics which you will
discover for yourself. We describe an assortment of such tactics next.
! as `if' | If there is a sentence of the form D ! C and the conclusion
is C then try to show D. C follows using !E . D ! C can be read as
C if D, from which the tactic gets its name.
make use of :S | If the conclusion is ?, then perhaps there is a
negative sentence :S that is available which could be used in a :E step
once S had been proved.
?E anywhere | If you cannot see what to do next perhaps you can
derive ? and then use ?E . This often happens in some branches of a
_E box, in those branches which `are not what the argument is about'
(for example, in the left-hand inner box of Figure 16.15).
combined _ rules | The _I and _E rules often go together | rst
use _E and then _I . Suppose the data is X _ Y and the conclusion is
C _ D. _E will force two subproofs, one using X and one using Y , and
perhaps in one you can prove C and in the other D. In both cases _I
will yield C _ D, as you required.
equivalence | Any sentence can be rewritten using an equivalence.
When lling in a proof downwards, data can be rewritten into new data
and when lling in a proof upwards, conclusions can be rewritten into
new conclusions.
theorem | Remember that it is possible to use theorems anywhere in a
proof, for these are previously proved sequents that do not depend on
any data and so could be used anywhere.
lemma | In some cases a large proof can best be tackled by breaking it
down into smaller steps. If your problem is to show Data ` Conclusion
then maybe you could show Data ` Lemma and then make use of
Lemma to show Conclusion | (Data and Lemma) ` Conclusion. The
choice of which lemma to prove is often called a `Eureka' step for it
sometimes requires considerable ingenuity.
excluded middle | If there are no negative sentences, then perhaps you
can introduce a theorem of the form Z _ :Z and immediately use _E .
230 Natural deduction
Of course, some ingenuity is needed to choose a suitable Z , but it is
worth trying Z as the conclusion you are trying to prove.
PC | Perhaps you can use the proof by contradiction derived rule.
If all else fails, use PC, or excluded middle.
And if all else does not fail then do not use PC | the negated
assumptions it introduces often make the proof more dicult to
understand.
Most practical proofs make use of three of the tactics on a large scale they
are the lemma, equivalence and theorem tactics:
The lemma tactic is used to break the proof into smaller steps.
The equivalence tactic is used to rewrite the data into the most
appropriate form for the problem.
The theorem tactic is used to make large steps in one go by appealing
to a previous proof.
In practice, we make use of hundreds of theorems, some of which are exercises
in this book and some of which you will discover for yourself. So watch out
for them!
16.3 Examples
The various rules and tactics of this chapter are illustrated in the following
examples.
Show :P ` P ! Q
1 :P
2 P
3 ? :E (1 2)
4 Q ?E (3)
5 P !Q !I
Figure 16.16 :P ` P ! Q
The derivation in Figure 16.16 is a useful one to remember. It is used in the
following example which derives a famous law called `Pierce's law' after the
logician Charles Pierce.
Examples 231
Show ` ((P ! Q) ! P ) ! P
Two proofs are given (in Figures 16.17 and 16.18) | the rst uses P _ :P
and the second uses PC . They both illustrate the benet of planning in a
proof. In the rst proof it is clear that the sentence (P ! Q) ! P will yield
P , the conclusion, if P ! Q can be proven. Also, the sentence P _ :P means
that since P can be derived from P , P ! Q will have to be proven from
:P . And we have shown this in Figure 16.16. In the second proof a useful
technique is used |`use PC if all else fails'. Applying it in this example
leads to the goal of ? | the necessary :E step will require a sentence and
its negation to be derived. :P is already an assumption so consider deriving
P . This can be done by deriving P ! Q, which follows from :P , again as in
Figure 16.16. Notice that here we have had to use some insight in order to
1 (P ! Q) ! P
2 :P _ P (Th)
3 :P P
4 P !Q (Fig. 16:16) P X (3)
5 P !E (1 4)
6 P _E (2)
7 ((P ! Q) ! P ) ! P !I
1 (P ! Q) ! P
2 :P
3 P !Q (Fig. 16:16)
4 P !E (1 3)
5 ? :E (2 4)
6 P PC
7 ((P ! Q) ! P ) ! P !I
1 A^B !C
2 :D ! :(E ! F )
3 C ! (E ! F )
4 A
5 B
6 A^B ^I (4 5)
7 C !E (6 1)
8 (E ! F ) !E (3 7)(a lemma)
9 :D
10 :(E ! F ) !E (2 9)
11 ? :E (10 8)
12 D PC
13 B!D !I
14 A ! (B ! D) !I
That is, nd the minimum of the rst two numbers and then the minimum
of this result and the third number. To show that the program meets the
specication, we must show that:
8x8y 8z: min3 x ^ min3 y ^ min3 z ^ (min3 = x _ min3 = y _ min3 = z )]
that is 2 3
min (min x y ) z x ^ min (min x y ) z y ^
6 (min x y ) z z ^ 77
8x y z: 6
64 (min (min xmin
y) z = x _ min (min x y ) z = y_ 75
min (min x y )z = z )
To show that a sentence is true for all x y z we should show that it is
true for any arbitrary values in place of x y z. (See Section 17.2.) Suppose
X Y Z are arbitrary values for x y z. Then we have to show
min (min X Y ) Z X ^ min (min X Y ) Z Y ^ min (min X Y ) Z Z ^
(min (min X Y ) Z = X _ min (min X Y ) Z = Y _ min (min X Y )Z = Z )
First, what are the initial assumptions? The specication of min for a start.
Any other assumptions can be added as the proof progresses. A look at the
sentence to be proved reveals that it is a conjunction of four sentences, so
each one has to be proved.
The rst is min (min X Y ) Z X: Use the specication of min | write
min X Y as u, then min u Z u ^ min u Z Z . (Since the result of
min X Y is a num, it satises the implicit pre-condition for the rst argument
of min in min (min X Y ) Z .) Also, u X ^ u Y . Hence, after using the
fact that is transitive, min u Z X , min u Z Y , min u Z Z . This
gives the rst three parts. The fourth is a disjunction.
One way to prove a disjunction is to use another. From the specication
of min, u = X _ u = Y , and min u Z = u _ min u Z = Z . Take the second
of these: min u Z = Z will yield the result after _I . Assuming now that
min u Z = u, from the rst disjunction there are two cases: u = X for one
case, and u = Y for the other. Together, u = X and min u Z = u give
min u Z = X , which again yields the result. The other case is similar. The
box proof is shown in Figure 16.20. (Notice that lines 7, 8, 9 and 10|14
give the derivations of the four conjuncts in line 16.)
234 Natural deduction
1 min u Z = u _ min u Z = Z
2 u=X_u=Y
3 uX^uY
4 u X u Y ^E (3)
5 min u Z u ^ min u Z Z
13
min u Z = X_ _I (12)
min u Z = X_ _I (12)
min u Z = Y_ min u Z =Y_
min u Z=Z min u Z=Z
14 min u Z = X _ min u Z = Y _ _E (2)
min u Z=Z
15 min u Z = X _ min u Z = Y _ min u Z = Z _E (1)
Figure 16.20
Summary 235
16.4 Summary
A valid argument consists of a collection of premisses and a conclusion
such that if the premisses are true then the conclusion must be true,
too.
The basic natural deduction rules for propositional sentences are given
in Appendix C.
The _I , ^E , !E , :E rules require some ingenuity, choosing which
rules to apply and when, whereas the ^I , _E , !I , :I rules are all
automatic, requiring just some preparation, and should be applied as
soon as you realize that they can be applied.
Derived rules can be useful, especially the rule PC , proof by contradiction.
Boxes are useful for structuring proofs and to show where assumptions
hold.
There are various tactics for nding derivations:
! as `if'
making use of :S
use ?E anywhere
PC
excluded middle
combined _ rules
equivalence
theorem
lemma
16.5 Exercises
1. Show
(a) ` P ^Q!P (b) P ` Q ! (P ^ Q)
(c) P ! Q :Q ` :P (d) :P ` P ! Q
(e) :P P _ Q ` Q (f) :I ^ :F ` :(I _ F )
(g) ` P ! (Q ! P ) (h) P ! S (P ! Q) ! S ` S
(i) F ! (B _ W ) :(B _ P ) W ! P ` :F
(j) P ! Q :P ! R Q ! S R ! S ` S
(k) (C ^ N ) ! T H ^ :S H ^ :(S _ C ) ! P ` (N ^ :T ) ! P
(l) R ! :I I _ F :F ` :R
(m) P ! (Q ! R) ` (P ! Q) ! (P ! R)
236 Natural deduction
In the preceding chapter we looked at natural deduction rules for the various
logical connectives. Each connective was associated with an introduction rule
for use in deriving a sentence involving the connective, and an elimination
rule for deriving further sentences from a sentence using the connective.
There are six more natural deduction rules to be introduced in this
chapter. Four of them cover the quantiers, which also have elimination and
introduction rules | 8I , 8E , 9I , 9E . The other two are for reasoning with
equality, which is an important predicate that has its own rules: eqsub, which
acts rather like an equality elimination rule, and reex, which acts like an
equality introduction rule.
Some examples
In our rst example, shown in Figure 17.1, we give a proof of
tired(lenny) ^ lion(lenny) ! does(lenny sleep). The initial data appears in
lines 1|3 and, after the automatic step of !I , several non-automatic
steps are made in lines 4|8. The 8!E rule is used several times. For
example, at line 7 8E is rst (implicitly) applied to line 1, to derive
lion(lenny) ! does(lenny, hunt) _ does(lenny, sleep) and then !E is applied to
derive does(lenny, hunt) _ does (lenny, sleep). After that, another automatic
step is made to prepare for _E .
The second example, shown in Figures 17.2 and 17.3, is a proof of an
existentially quantied sentence 9x: :shot(x Diana). The initial data given in
lines 1|4 can be used to show the conclusion in two dierent ways. The
simpler way is given rst. This example is typical of real situations when more
data than is required to prove the given goal is available, making ingenuity
even more necessary in nding the proof.
The rst derivation proves that Diana did not shoot herself, and the second
that Janet did not shoot Diana. The combined rule 8:E is used in the second
derivation at line 8 | the new conclusion inhouse(Janet) ^ ingarden(Janet) is
derived because if this is proved then 8E using line 3 will give a contradiction.
All uses of 8E and 9I require some insight into which substitutions for the
bound variable will prove suitable. In this case there are two names, Janet
and Diana, and either might be appropriate.
240 Natural deduction for predicate logic
1 8x: :shot(x x)
2 inhouse(Janet)
3 8x: :(inhouse(x) ^ ingarden(x))
4 8x: shot(x Diana) ! ingarden(x)]
5 :shot(Diana Diana) 8E (1)
6 9x: :shot(x Diana) 9I (5)
1 8x: :shot(x x)
2 inhouse(Janet)
3 8x: :(inhouse(x) ^ ingarden(x))
4 8x: shot(x Diana) ! ingarden(x)]
5 shot(Janet Diana)
6 ingarden(Janet) 8!E (4 5)
7 inhouse(Janet) ^ ingarden(Janet) ^I (2 6)
8 ? 8:E (7 3)
9 :shot(Janet Diana) :I
10 9x: :shot(x Diana) 9I (9)
1 P (a) _ P (b)
2 8x: P (x) ! Q(x)]
3 P (a) P (b)
4 Q(a) 8!E (2 3) Q(b) 8!E (3 2)
5 9x: Q(x) 9I 9x: Q(x) 9I
6 9x: Q(x) _E (1)
1 :P (b) ! P (a)
2 8x: P (x) ! Q(x)]
3 :P (b) _ P (b) (Th)
4 :P (b) P (b)
5 P (a) !E (1 4) Q(b) 8!E (2 4)
6 Q(a) 8!E (2 5) 9x: Q(x) 9I (5)
7 9x: Q(x) 9I (6)
8 9x: Q(x) _E (3)
1 8x : num: P (x)
2 P (25) 8E (1)
3 9x : num: P (x) 9I (2)
A proof of 8x: P x] can be obtained from a proof of P c] for some
new constant c.
8-introduction (8I ) and 9-elimination (9E ) rules 243
c 8I
...
P c]
8x: P x] (8I)
or typed
c 8I is-t(c)
...
P c]
The `new' means that c is introduced for the rst time inside the box
that contains the subproof of P c] c is only available within that box and it
cannot be mentioned outside it. So, in particular, c cannot occur in 8x: P x].
The c 8I in the left-hand corner is a reminder that c must be new.
The version using a typed quantier is derived from the untyped version
and !I using the translation of 8x : t: P x] into 8x: is-t(x) ! P x]].
The 8I rule is completely automatic and is used in a backwards direction
from goal to subgoal. The motivation behind this rule is the commonly quoted
law:
If one can show P u] for an arbitrary u, then 8x: P x] holds.
The use of a new term for c implements the `arbitrary' part of the law.
The following is an informal explanation of why the rule `works': in order
to derive 8x: P x], the derivation should work for whatever value v could be
substituted for x and should not depend on properties of a particular v. Since
c is new, any data that is used to prove P c] will not mention c and the
derivation cannot rely on special properties of c (apart from that it is of type
t), as there are none. Properties are either not relevant or are completely
general, of the form 8 , in which case they apply to any value.
A very common pattern used in quantied sentences is 8x: P x] ! Qx]].
If this sentence is a conclusion then two automatic steps are immediately
applicable | rst a 8I step and then a !I step. These can be combined
into one step, 8!I , that requires just one box instead of two, as is done
implicitly in deriving a typed version of the 8I rule.
Remember that in Chapter 15 we encountered a diculty in checking
whether a universal sentence was true when there was an innite number of
values to check? Well, now we have an alternative approach. The sentence is
checked for one or more arbitrary values which between them cover all the
possible cases. For example, to show that 8x : int: P x], we might try to
show that P c] for an arbitrary integer c. Now, any integer is either < 0,
= 0 or > 0, so we could try to show that P c] is true in each of the three
cases. (Alternatively, any integer is also prime or non-prime, so we could try
to show that P c] is true in those two cases.)
244 Natural deduction for predicate logic
9-elimination
The 9E rule is another completely automatic rule that introduces a new
constant into a proof. It may seem a little dicult at rst sight and you
should thus learn it by heart and understand why it appears as it does.
To derive Q using 9x: P x], derive Q using P c], where c is a new
constant.
The format for the 9E rule is:
9x: P x]
c9E P c]
...
Q
Q (9E )
or typed
9x : t: P x]
c9E P c]
is-t(c)
...
Q
Q (9E )
The version using a typed quantier is derived from the untyped version and
^E using the translation of 9x : t: P x] into 9x: is-t(x) ^ P x]].
Again, c must be a new constant and the box is used to indicate where c
is available. In particular, the conclusion Q must not mention c. Notice that
the conclusion appears twice outside the box it is justied by 9E and inside
by something else. The rule is best applied as soon as possible in a proof so
that the new constant c is available as soon as possible.
An informal explanation of why the rule works is as follows: in order to
use 9x: P x] a name has to be given to x the `x that makes P x] true'.
Although it would be possible to keep referring to this value as `the x that
makes P x] true', this is a very cumbersome name and also one that could
be ambiguous if there were more than one such x, so a new constant c is
introduced. c must be new since all that is known about it is that P c] is
true (and if the quantier is typed that c is of type t). If c were not a
new constant, then the proof of Q might inadvertently use some additional
properties that were true of some values but not all, and it could be that the
`x that makes P x] true' was one of those values for which these additional
properties were not true.
8-introduction (8I ) and 9-elimination (9E ) rules 245
Some more examples
In this section we look at some typical examples involving sentences with
quantiers.
Show 9y: 8x: P (x y) ` 8u: 9v: P (u v) (Figure 17.7)
`If there is some y that makes P (x y) true for all x, then for every u there
is some v (the same one for each case) that makes P (u v) true.'
The rst two steps, 8I and 9E , are automatic but could easily have been
in the opposite order. Once a and b have been introduced there are enough
clues in the proof so far (lines 1|3 and 5|7) to ll in the gap. Notice that
the reverse deduction is not valid:
8u: 9v: P (u v ) 0 9y: 8x: P (x y )
2 that is-num(A) is part of the preparation for 8I. Since we only want to
show R(w w) for all numbers, A can be an arbitrary number. In turn, to use
the sentence at line 1 requires a check that the terms substituted for x y are
both numbers. They are, for both x y are replaced by A. At line 5 a check
must be made that 1 is a number before applying 9I . Finally, all the rules
of arithmetic apply.
Equality 247
Does 8x: P (x) ` 9x: P (x)? (Figure 17.10)
If you try to show this using natural deduction you will nd that you cannot
get started because you have no knowledge that any individuals exist and so
cannot make any substitutions in the 8E or 9I rules. In order to show the
conclusion you must add to the data the sentence 9z: >, where > is the
sentence that is always true. If you think about it, it is no real surprise that
1 8x: P (x)
2 9z: >
I 9E 3 > I exists
4 P (I ) 8E
5 9y: P (y ) 9I
6 9y: P (y ) 9E
17.3 Equality
The equality relation `=' is a predicate that is very commonly used and
everyone has a fairly good idea of what a = b is supposed to mean | that
a and b denote the same element or individual. This in turn means that
whatever properties are possessed by a will also be possessed by b. So, for
example, if
Dr Jekyll = Mr Hyde
Mr Hyde killed someone
then it can be deduced that Dr Jekyll killed someone. For, if the sentence
9x: killed (Mr Hyde x) is satised by Mr Hyde, then it is also satised by
Dr Jekyll, that is, 9x: killed(Dr Jekyll x). The example illustrates the main
rule for reasoning with equality | the rule of equality substitution | which
allows one side of an equation to be substituted for the other. An equality
atom such as Susan = Sue is often called an equation.
248 Natural deduction for predicate logic
Using equality in translation
Let us look rst at how equality can be used in sentences to express sameness,
uniqueness and functionhood.
Consider the following short propositions:
1. Tig eats vegetables
2. Tig only eats vegetables
3. Tig dances with Jig
4. Tig only dances with Jig
The straightforward translations of the rst two into logic are
1. 8x: vegetable(x) ! eats(Tig x)]
2. 8x: eats(Tig x) ! vegetable (x)]
If the third and fourth sentences are paraphrased in a similar way then they
become
8x: x = Jig ! dances-with(Tig x)]
and
8x: dances-with(Tig x) ! x = Jig]
An equation is used to express the proposition that `x is Jig', that is,
x = Jig. The third sentence can be rewritten equivalently and more naturally
as dances-with(Tig Jig).
Equality is also used to express uniqueness. For example, suppose we
wanted to express in logic the sentence
There is exactly one green bottle.
This sentence says the following:
1. There is at least one green bottle.
2. There is at most one green bottle.
And in logic we have
9x: greenbottle (x) ^ :9u: 9v: greenbottle(u) ^ greenbottle(v ) ^ u 6= v ]
An alternative and equivalent expression is obtained by paraphrasing the
sentence as
There is a greenbottle x and all greenbottles are the same as x
which in logic is
9x: greenbottle(x) ^ 8u: greenbottle(u) ! u = x] ]
The rst approach" can be generalized for n 1 greenbottles: #
greenbottle (x1 ) ^ : : : ^ greenbottle (xn )
9x1 : : :xn ^x 6= x ^ : : : ^ x 6= x ^
2 1 2 ; n n 1 3
greenbottle(u0) ^ : : : ^ greenbottle(un )^
:9u0 : : : un 64 u0 6= u1 ^ u0 6= u2 ^ : : : ^ u1 6= u2 ^ : : : 75
^ : : : ^ un 6= un 1
;
Substitution of equality 249
The second approach 2 can also be generalized: 3
greenbottle(x1) ^ : : : ^ greenbottle (xn)^
9x1 : : : xn 64 x1 6= x2 ^ : : : ^ xn 6= xn 1 ^
;
75
8u: greenbottle(u) ! u = x1 _ : : : _ u = xn ]
It is not always necessary to use equality to express `sameness'. For
example, `a and b have the same parents' might be written as
8x: parent-of(x a) $ parent-of(x b)]
Actually, the logic only says that `if a and b have any parents then they have
the same ones', and to express that a and b have some parents (as implied
by the English) we must add
^9x: parent-of(x a)]
Equality is also used in expressing that a particular relation is a function.
For example, the relation mother-of(x y) is a function of y | for each y
there is just one x that is related to it. This is expressed as
8y: 8x: 8z: mother-of(x y ) ^ mother-of(z y ) ! z = y ]
If, in addition, we state that `everyone has a mother'
8y: 9x: mother-of(x y )
then it is possible to simplify sentences such as
8u: mother-of(u Ann) $ mother-of(u Jeremy)]
to
9u: mother-of(u Ann) ^ mother-of(u Jeremy)]
See Exercise 9.
a b8I 1 a=b
2 a=a reex
3 b=a eqsub
4 8x: 8y: x = y ! y = x] 8!I
..
1 .
2 a=b
..
3 .
4 S a]
5 S b] eqsub
Figure 17.12
Figure 17.13
always the case for sentences of this sort which have conditions involving an
equation with at least one variable argument. For example,
8x y: x = a ^ y = b ^ P (x y ) ! Q(x y )]
will yield the simpler P (a b) ! Q(a b).
In a similar way, 9x: x = a ^ P (x)] $ P (a) is also true.
Rewrite proofs
A method of showing that an equation is true, familiar from school
mathematics, is to use rewriting. That is, to show a0 = b, a0 is rewritten into
Figure 17.15
proof uses some properties of rev, one occurrence of the reex rule and
several applications of eqsub. It has the general pattern shown in Figure 17.16,
where at each step eqsub is used to rewrite either the left or right side of
an equation. (So either ai is identical to ai 1 or bi is identical to bi 1.) The
; ;
various equations
..
.
an = a n reex
an 1 = bn 1
; ; eqsub
..
.
a1 = b1 eqsub
a0 = b0 eqsub
Figure 17.16
Delete
We will illustrate the various features of natural deduction by proving that
the del program meets its specication (that is, it deletes the rst occurrence
Substitution of equality 253
various equations
..
.
a0 = a1
= a2
..
. = an
= bn 1
;
..
. = b0
Figure 17.17
of c from l). First of all the program and specication:
del :: * -> !* ]-> !* ]
||pre: c belongs to l
||post: (E)m,n:!* ]!z=m++n & l=m++!c ]++n &
|| not(c belongs to m)]
|| where z= del c l
del c (h:t) = t, c=h
= (h: del c t), c 6 t =
Now the proof | the outline structure is given in Figure 17.18 and the
two cases for the induction step are given in Figures 17.19 and 17.20. In the
proof we use the following abbreviations:
P (l) 8c :
: c 2 l ! Q(l)]
and
Q(l) 9m n : !
]del c l = m++n ^ l = m++!c]++n ^ :c 2 m]
We also give the proof in English for comparison.
Proposition 17.1 del satises its specication. We have to show 8l : !
]: P (l)
and we use induction on l and show P (!]) and P (h:t).
The base case P (!]) is vacuously true because c 2 !] is always false. For
the induction step we can assume as hypothesis P (t):
8c: c 2 t ! 9m n : !
]del c t = m++n ^ t = m++!c]++n ^ :c 2 m]]
So, x c as a constant C and suppose C 2 h:t. There are two cases:
either C = h or C 6= h. If C = h then l = ! ]++!C ]++t with C 2= ! ], and
by denition del C l = t = ! ]++t. Hence we can take m = ! ] n = t. If C
6= h then notice that because C 2 h:t we must have C 2 t and hence by
the hypothesis there is some m1 and n1 such that
del C t = m1++n1 ^ t = m1++!C ]++n1 ^ :C 2 m1]
254 Natural deduction for predicate logic
1 First part of _E
2 C=h
del C (C :t) = ]++t^
3
(C :t) = ]++C ]++t ^ :C 2 ] ^I (del C (C :t) = t)
del C (h:t) = ]++t^
4 (h:t) = ]++C ]++t^ eqsub(2)
:C 2 ] 2 3
del C (h:t) = m++n^
5 9m n : ] 6
4 (h:t) = m++C ]++n^ 75 9I (m = ] n = t)
:C 2 m
Figure 17.19
1 second part of _E
2 C 6= h
3 C2t 2 3 (C 6= h and C 2 h:t)
del C t = m++n^
4 9m n : ] 6
4 t = m++C ]++n^ 75 !E (hypothesis)
:C 2 m
del C t = m1++n1^
m1 n19E 5 t = m1++C ]++n1^
:C 2 m1
6 del C t = m1++n1 ^E
7 (h:del C t) = (h:m1)++n1 properties of lists
8 del C (h:t) = (h:m1)++n1 program
9 t = m1++C ]++n1 ^E
10 (h:t) = (h:m1)++C ]++n1 properties of lists
11 :C 2 m 1 ^E
12 :C 2 (h:m1) (C 6= h)
del C (h:t) = (h:m1)++n1^
13 (h:t) = (h:m1)++C ]++n1^ ^I
:C 2 (h:m1)
14 Q(h:t) 9I (m = (h:m1) n = n1)
15 Q(h:t) 9E (4)
Figure 17.20
17.5 Summary
The natural deduction rules for quantiers are collected in Appendix C.
The rules 8I and 9E are automatic, whereas 8E and 9I are not and
require some ingenuity in their use. A useful tactic for dealing with
quantiers is
Apply the automatic 8I and 9E rules as soon as possible for
they will yield constants that can be used in 9I and 8E steps
later.
It can be helpful to apply equivalences to quantied sentences so
that the quantiers qualify the smallest subsentences possible. For
example, 8x: (9y: Q(x y)) ! P (x)] might be easier to deal with than
8x: 8y: Q(x y ) ! P (x)].
The eqsub and reex natural deduction rules are also listed in Appendix C.
256 Natural deduction for predicate logic
Equality is used to express uniqueness and functionhood.
The equality rules can be used to show the symmetry and transitivity
of =.
The equality rules can be used to give a rewrite proof.
17.6 Exercises
1. Show:
(a) dragon(Pu), 8x: dragon(x) ! y(x)] ` 9x: y(x)
(b) 8x: :(man(x) ^ woman(x)),
man(tom), woman(jill) woman(sophia) ` 9x: :man(x)
(c) 8x y: arc(x y )] ! path(x y ),
8x y: 9z: arc(x z ) ^ path(z y )] ! path(x y )],
arc(A B ) arc(B D) arc(B C ) arc(D C ) ` 9u: path(u C )
How many dierent proofs are there?
(d) 8x y z: R(x y ) ^ R(y z ) ! R(z x)], 8w: R(w w)
` 8x y: R(x y ) ! R(y x)]
(e) On(A B ) On(B C )
8x: :(Blue(x) ^ Green(x)) Green(A) Blue (C ),
8x y: On(x y ) ^ Green(x) ^ :Green(y ) ! Ans(x y )]
` 9x: 9y: Ans(x y )
(f) 8x y: 8z z 2 x ! z 2 y ] ! x y ],
8x: :(x 2 ?) 8y: y 2 U ` 8r: ? r ^ 8s: s U ^ 8t: t t
2. Show
8x y z: less(x z ) ^ less(z y ) ! between (x y z )],
8x: less(x s(x)), 8x y: less(x y ) ! less(x s(y ))]
` (a) ^ (b) ^ (c)
where
(a) =between(s(0) s(s(s(0))) s(s(0)))
(b) =9x: between(0 x s(0)) ^ between(s(s(0)) s(s(s(s(0)))) x)]
(c) =9x: 9y: between(0 x y) ^ between(s(0) s(s(s(0))) x)]
3. Use Natural Deduction to show:
(a) 8x: :P (x) ` :9x: P (x)
(b) :9x: P (x) ` 8x: :P (x)
(c) 8x: F (x) ^ G(x)] ` 8x: F (x) ^ 8x: G(x)
(d) 8x: F (x) _ 8x: G(x) ` 8x: F (x) _ G(x)]
Exercises 257
(e) 9x: F (x) ^ G(x)] ` 9x: F (x) ^ 9x: G(x)
(f) 9x: F (x) _ 9x: G(x) ` 9x: F (x) _ G(x)]
(g) 8x y: F (x y ) ` 8u v: F (v u)
(h) 9x: 9y: F (x y ) ` 9u: 9v: F (v u)
(i) 9x: 8y: G(x y ) ` 8u: 9v: G(v u)
(j) 8x y: S (y ) ! F (x)] ` 9y: S (y ) ! 8x: F (x)
(k) 8x: :P (x) ` :9x: P (x)
(l) :8x: P (x) ` 9x: :P (x)
(Hint: assume that :9x: :P (x) and derive a contradiction this
time the only way to use the negated premiss.)
(m) P ! 8x: Q(x) ` 8x: P ! Q(x)]
(n) 9x: P ! Q(x)] ` P ! 9x: Q(x)
(o) P ! 9x: Q(x) , 9z: > ` 9x: P ! Q(x)]
(p) (9x: P (x)) ! Q ` 8x: P (x) ! Q]
(q) 9x: P (x) ! Q] ` (8x: P (x)) ! Q
(r) 8x: P (x) ! Q, 9z: > ` 9x: P (x) ! Q]
(s) 8x: F (x) _ G(x)] ` 8x: F (x) _ 9y: G(y).
(Hint: use 8x:F (x) _ :8x: F (x).)
(t) 8x: 9y: F (x) _ G(y)], 9z: > ` 9y: 8x: F (x) _ G(y)]
(Hint: use the theorem X _ :X where X is the conclusion
9y: 8x: F (x) _ G(y )].)
4. Show by natural deduction
(a) 8x: P (a x x) 8xy z: P (x y z) ! P (f(x) y f(z))] ` P (f(a) a f(a))
(b) 8x: P (a x x) 8xy z: P (x y z ) ! P (f(x) y f(z))]
` 9z: P (f(a) z f(f(a)))]
(c) 8y: L(b y) 8x z: L(x y) ! L(s(x) s(y))] ` 9z: L(b z) ^ L(z s(s(b)))]
5. One of the convenient ideas incorporated in Natural Deduction is that
it is possible to use `derivation patterns' (or derivation schemes) for
example, the pattern :A A _ B ` B can be derived. Such schemes
enable larger steps to be taken in a proof than are possible using only
the basic rules. If the scheme is very common it is sometimes called
a derived rule and given a name. (The benet lies in the fact that
any sentence can be substituted throughout the scheme for A or B (for
example) and the scheme remains true. For example, in (a) below we
could have 8x: P (b x) ! Q(x x)] P (b a) ` Q(a a) .)
258 Natural deduction for predicate logic
Some useful schemes are given below in each case give a Natural
Deduction proof of the scheme. The notation P x] means that x occurs
in the arguments of P if P is a predicate, or, more generally, in P if it
is a sentence:
(a) 8x: P x] ! Qx]] 9x: P x] ` 9x: Qx] or 8x: P x] ! Qx]] P a] `
Qa], where a is a constant
(b) 8x: P x] ^ Rx] ! Qx]] P a] Ra] ` Qa], where a is a constant.
Why doesn't 8x: P x] ^ Rx] ! Qx]] 9x: P x] 9x: Rx] ` 8x: Qx]
work?
Collecting lots of these schemes together enables more concise
derivations to be obtained that are still sure to be correct. There
are lots of schemes for arguing about arrays, too. For example,
(c) If n > 0 then 8i0 < i < n + 1 ! P i]] ` 8i0 < i < n ! P i]] ^ P n]
holds in both this direction and the opposite one and is useful for
dealing with situations when P i] is a sentence about array values.
6. Use natural deduction to show the following:
(a) 8x: x = a _ x = b] , :P (b), Q(a) ` 8x: P (x) ! Q(x)]
(Hint: Use the _E and ?E rules.)
(b) (1) 8x: :B (x x) ` 8x: 8y: B (x y) ! x 6= y]
(2) 8x: 8y: B (x y) ! x 6= y] ` 8x: :B (x x)
(c) KB is either at home or at college, KB is not at home
` home 6= college.
(d) Everyone likes John, John likes no-one but Jack ` John = Jack.
(e) S is green, S is the only thing in the box
` Everything in the box is green.
(f) 8x: 8y: 8z: R(x y) ^ R(x z) ! z = y], R(a b), b 6= c ` :R(a c).
(g) a = b _ a = c, a = b _ c = b, P (a) _ P (b) ` P (a) ^ P (b)
(h) ` 8x: 9y: y = f (x)
(i) ` 8y: y = f (a) ! 8z: z = f (a) ! y = z]]
(j) 8x: x = a _ x = b], g (a) = b,
8x: 8y: g (x) = g (y ) ! x = y ] ` g (g (a)) = a
(Hint: You will need to use 8E in the rst sentence with g(b)
substituted for x.)
7. Express in logic:
(a) For each x there is at most one y such that y = f (x).
(b) For each x there is exactly one y such that y = f (x).
Exercises 259
8. Show (a) (1) ` (2), (b) (2) ` (3) and (c) (3) ` (1) by natural deduction:
(1) 9x: g(x) ^ 8z: g(z) ! z = x]]
(2) 9x: 8z: g(z) $ z = x]
(3) 9x: g(x)] ^ 8z: 8y: g(z) ^ g(y) ! z = y]
9. Show by natural deduction that
8y: 8x: 8z: mother-of(x y ) ^ mother-of(z y ) ! z = y ]
8y: 9x: mother-of(x y )
8u: mother-of(u Ann) $ mother-of(u Jeremy)] $
` 9u: mother-of(u Ann) ^ mother-of(u Jeremy)]
10. Identify the corresponding steps between the English and box proofs in
Section 17.4, in which it was shown that del meets its specication.
11. Give Miranda programs for the functions given below and then use box
proofs to prove, using induction if appropriate, that the functions meet
their specications. That is, show that the specication follows from
any assumed pre-conditions and the execution and termination of the
program. (Show that the program terminates as well.)
(a) last :: !char] -> char last x is the last character of x
8x : !
]: x 6= !] ! 9y : !
]: x = y ++last x] ]
(b) odd:: num -> num odd x is the least odd number larger than x
" #
8x : num
odd ( odd x) ^ x < odd x^
:9y : num: odd(y ) ^ y > x ^ y < odd x]
(c) prime:: num ! Bool prime x is true i x is prime
8x : num: prime x $ :9z : num: divisor(z x) ^ z 1 ^ z < x]]
(d) uni:: !char] -> Bool: uni x is true i x has no duplicates
2 3
uni x $ :9y : char:
8x : char] 64 9m : !char]: 9n : !char]: 9p : !char]: 75
!x = m++!y ]++n++!y ]++p]
Chapter 18
Models
1 8x: P (x x)
a8I 2
..
b8I 3 .
4 fcannot show P (a b)g
5 P (a b)
6 8y: P (a y ) 8I
7 8u: 8y: P (u y ) 8I
1 9x: P (x)
b8I 2
a9E 3 P (a)
..
4 .
5 fcannot complete proofg
6 P (b)
7 P (b) 9E (1)
8 8x: P (x) 8I
1 9z: >
2 8x: 9y: P (x y)
c9E 3 >
4 9y: P (c y ) 8E (2)
d9E 5 P (c d)
e8I 6
..
7 .
8 fcannot ll gapg
9 P (c e)
10 8v: P (c v ) 8I
11 9u: 8v: P (u v ) 9I
12 9u: 8v: P (u v ) 9E (4)
13 9u: 8v: P (u v ) 9E (1)
Figure 18.4 Failure to show f9z: > 8x: 9y: P (x y)g ` 9u: 8v: P (u v)
at least two elements | say fc dg with P (c d) and P (d c) both true and
P (c c) and P (d d) both false. Then the premisses are true but the conclusion
is false.
On the other hand, after line 5 has introduced d we can use it to deduce
9y: P (d y ), which leads to another 9 to eliminate and so on. The alternative
Intended structures 267
proof attempt is shown in Figure 18.5. We can write down the constants that
1 9z: >
2 8x: 9y: P (x y)
c9E 3 >
4 9y: P (c y ) 8E (2)
d9E 5 P (c d)
6 9y: P (d y ) 8E (2)
e9E 7 P (d e)
..
8 .
9 fcannot ll gapg
10 9u: 8v: P (u v )
11 9u: 8v: P (u v ) 9E (6)
12 9u: 8v: P (u v ) 9E (4)
13 9u: 8v: P (u v ) 9E (1)
Figure 18.5 Failure to show f9z: > 8x: 9y: P (x y)g ` 9u: 8v: P (u v)
arise in Figure 18.5, with an arrow from x to y whenever P (x y):
c ! d ! e !
This suggests an innite model:
Domain = set of natural numbers f0 1 2 3 g
P (x y) means that y = x + 1
It is indeed a counter-example. You cannot possibly choose u so that
8v: P (u v ), for you never obtain P (u 0).
18.4 Equivalences
In Chapter 15 we dened two sentences S and T to be equivalent (S T )
if they had the same truth-value as each other in every situation. What we
meant, was that
S T i
in each structure for fS T g S and T are either both true or both false
that is, S $ T is true in every structure (it is a tautology)
that is, S j= T and T j= S
The last property holds, since, if it is not possible to have S true in any
structure of fS T g and T false, or T true and S false, then in any structure
which makes S true T must be true, too, and in any structure which makes
T true then S must be true, too. Hence S j= T and T j= S .
We now take a second look at some quantier equivalences and see how the
important property of equivalent sentences, that they can be substituted for
each other in any context, is aected.
In many cases, the same principles as before apply. A constituent of a
sentence can be replaced by any other equivalent sentence. For example,
Equivalences 269
:8x: P (x) 9x: :P (x) and any occurrence of the rst sentence can be
replaced by the second, or vice versa. So from S _ :8x: P (x) we can obtain
S _ 9x: :P (x). This applies as long as there is no nested reuse of variables,
for example, 8x: 9x , but remember we said we would not allow such forms.
(They can always be avoided by renaming variables.)
If you cannot remember a useful equivalence it does not matter, for you can
always derive it each time you need it. The only disadvantage is the extra
time taken! Several useful quantier equivalences are given in Appendix B
and although most of the equivalences were stated for unqualied quantiers,
qualied quantiers present no problem and behave quite well. For example,
the equivalence above also holds in the form :8x : N: P (x) 9x : N: :P (x).
In any quantier-free sentence S any subsentence may be replaced by an
equivalent sentence without aecting the meaning of S . This is very useful as
one form of a sentence may be more convenient than another. For example,
:(P _ Q) may not be as useful a sentence form in a natural deduction proof
as the equivalent :P ^ :Q, which can be broken into two smaller pieces,
:P and :Q, and 8x: :P (x) is almost always more useful than :9x: P (x).
Many equivalences, such as those given in Appendix B, once instantiated by
replacing F , G etc., by particular sentences, can be used as they stand to
replace one side of the equivalence by the other.
The quantiers 8 and 9 also respect equivalences:
if F (a) G(a) then
8x: F (x) 8x: G(x), and
9x: F (x) 9x: G(x)
(Exercise 9 asks you to prove this.)
For example, since (F (a) ^ G(b)) (G(b) ^ F (a)), 9y: F (a) ^ G(y)]
9y: G(y ) ^ F (a)] and 8x: 9y: F (x) ^ G(y )] 8x: 9y: G(y ) ^ F (x)].
In Sections 18.6 and 18.7 we show that A j= B i A ` B and hence we
have A B i A ` B and B ` A. An equivalence proof is therefore a good
way to show A ` B | show instead the stronger A B using equivalences.
Reasoning using equivalences can also be a useful way of making progress in
a proof. That is, from
S S1 and S1 S2 and and Sn 1 Sn
;
Equivalence proofs are very helpful within natural deduction proofs for they
allow premisses and conclusions to be rewritten to more useful forms. There
are many useful `half-equivalences', that is, true sentences of the form A j= B ,
and some are shown in Figure 18.6.
1 9x: 8y: F (x y ) j= 8y: 9x: F (x y )
2 8x: F (x) _ 8y: G(y ) j= 8x: F (x) _ G(x)]
3 9x: F (x) ^ G(x)] j= 9x: F (x) ^ 9x: G(x)
4 8x: F (x) ! G(x)] j= 8x: F (x) ! 8x: G(x)
5 8x: F (x) ! G(x)] j= 9x: F (x) ! 9x: G(x)
6 8x: F (x) $ G(x)] j= 8x: F (x) $ 8x: G(x)
7 8x: F (x) $ G(x)] j= 9x: F (x) $ 9x: G(x)
Figure 18.6 Useful implications
In particular, if the data contains , and j= ', then ' can be added to
the data. Using half-equivalences to replace subsentences is possible but there
are some dangers. Exercise 10 considers this.
A natural deduction view of equivalence
Natural deduction gives another view of equivalences. For example, the proof
obligations of the two sentences 8x: F (x) ! S ] and 9x: F (x) ! S ], which are
shown in Figure 18.7, are essentially the same. Here, the proof obligation is
to show S from the data F (c), where c is a new constant in the proof. Hence
either of the original sentences behaves as a conclusion in a proof essentially
in the same way. If you try a similar exercise for other equivalences you will
often see that they exhibit the same kind of pattern | the proof obligation
for a pair of equivalent sentences is rather similar.
Equivalent sentences, however, also operate in essentially the same way
when used as data. For example, if the two sentences 8x: F (x) ! S ] and
9x: F (x) ! S ] were part of the data their use would lead to the fragments
shown in Figure 18.8. Here, the proof obligations amount to showing F (a) for
some a in the current context. These examples, although not a proof, should
help to convince you that equivalent sentences often `behave in a natural
deduction proof in the same kind of way'.
Soundness and completeness of natural deduction 271
c8I
F (c)
..
.
S
F (c) ! S !I
8x: F (x) ! S ] 8I
9x: F (x)
c9E F (c)
..
.
S
S 9E
9x: F (x) ! S !I
Figure 18.7
..
.
8x: F (x) ! S ]
..
.
F (a)
S 8!E
9x: F (x) ! S
..
.
F (a)
9x: F (x) 9I
S !E
Figure 18.8
18.5 Soundness and completeness of natural deduction
In this section we consider the two important properties of natural deduction,
soundness and completeness.
272 Models
One of the uses of natural deduction is as a technique for showing that
S j= T for sentences S and T . It is successful mainly because natural
deduction is sound:
If S ` T then S j= T
This is obviously a necessary property, otherwise all manner of sentences T
might be shown to be proven from S regardless of any semantic relationship,
and natural deduction would be useless.
At least, therefore, we can be sure that natural deduction proofs are correct.
But there could still be a problem. Perhaps, for a particular pair of sentences
S 1 and S 2, we cannot seem to nd a proof. We may ask whether we have
enough natural deduction rules to make a deduction. Well, in fact we do,
because of completeness:
If S j= T then S ` T
So we know there should be a proof.
Since we probably do not happen to know whether or not S 1 j= S 2, and
hence whether or not a deduction should be possible or not, then it might
be worth looking for a counter-example model if our proof attempts were
oundering. Completeness is not such a crucial property as soundness | for
it might be good enough in practice to be able to nd a proof in most of
the cases for which we expect to nd one.
Natural deduction is just one method that can be used to answer the
problem `does P j= C ' and there are other methods which are not considered
in this book. But natural deduction cannot be used to answer the question
`does P 2 C '.
We say that a problem with the property that there is some method which
can always decide correctly between `yes' and `no' answers is decidable. In our
problem is there some method that, given P and C , always tells you `yes'
when P j= C and `no' when P 2 C ? In this case, there is no method that will
always give the correct answer. Some methods may, like natural deduction,
always answer yes correctly, and may even be able to answer no correctly for
some cases, but no method can answer correctly in all cases. The problem,
then, of checking whether P j= C is called semi-decidable. A decidable problem
would be one for which a method existed which correctly `answered' both yes
and no type questions.
The problem of checking whether P j= C when P and C are propositional
is decidable, for then a method that checks all interpretations for the symbols
in fP C g is possible and is essentially the method of truth tables.
Proof of the soundness of natural deduction 273
18.6 Proof of the soundness of natural deduction
In this section the important soundness property of Natural Deduction is
proved:
if A ` B then A j= B soundness
that is, if a conclusion B is derivable from premisses A then it should be |
the argument is valid.
The underlying idea is quite simple: when you read a proof from top
to bottom (not jumping backwards and forwards in the way that it was
constructed) you see a steady accumulation of true sentences. Each new one
is justied on the basis that the preceding ones used as premisses by the
rule you are applying have themselves already been proved and so are true.
(This is an induction hypothesis! It uses induction on the length of the
proof, because the earlier sentences were proved using shorter proofs. Also,
disregard the fact that some parts of the proof are written out side by side
| rearrange them one after the other.)
For instance: consider ^E . If you have already proved A ^ B , by induction
you know that it is true (given the premisses) and it follows | check the
truth tables if you are really in doubt | that the A delivered by ^E is also
true.
This is the basic idea, but it is all made much more complicated by the
boxes. The problem is that `true' here means `true in every model of the
premisses', but the class of models varies throughout the proof. Each sentence
A appearing in the proof is proved in a context of constants and premisses:
the constants are not only those posed in the question (by being mentioned
in the overall premisses and conclusion), but are also those introduced by 8I
or 9E at the tops of boxes containing A and the premisses are not only the
overall premisses but are also the assumptions introduced for _E , !I , :I or
9E at the tops of boxes containing A.
What you introduce as a new constant or a new assumption at the top of
a box is part of the context of everything inside the box.
To take proper account of both premisses and context, we shall, for the
time being, use more rened notions of models and semantic entailment (j=).
A model for a context (S P ) (S the set of constants, P the set of premisses
the constants in sentences in P must all be in S ) is a model for P with
interpretations given for all constants in S . Then we write P j=S C to mean
that C is true in every model of (S P ).
Note the following: if (S P ) is a bigger context than (S P ) | all the
0 0
constants and premisses from (S P ) and possibly some more | then any
model of (S P ) is also a model of (S P ). (Exercise: prove this.) It follows
0 0
giving a contradiction.
6. A is the result of the construction so far, S is the context so far and
0 0
contradictory.
The construction of A+ will be an innite process unless there are no
function symbols in A (because of step (5)).
Finally, we have to show that the model formed by considering atoms and
their negations in A+ is still a model of A+. The atoms we consider are
all atoms formed from predicates in A and terms using symbols in the nal
context S + of A+. The domain of the interpretation I is just the set of terms
formed from symbols in S + and each term is interpreted by itself.
The additional cases cover Y being either of the form 9x: P x] or 8x: P x]:
Y could be of the form 8x: P x]? No, as then some sentence of
the form P t=x] would also be false and this is smaller than Y .
Y could be of the form 9x: P x]? No, as then every sentence
of the form P d=x] would be false, where d 2 domain of I .
In particular, P e=x] would be false, a contradiction as this is
smaller than Y .
18.8 Summary
A signature is a collection of extralogical symbols (predicates, functions
and constants) with their arities.
A structure (for a signature or for some sentences) gives concrete
interpretations for those symbols as relations, functions or elements from
some particular set, the domain.
Once this is done, any sentence using those symbols is interpreted and
it can be determined whether it is true or false.
A model for a sentence is a structure in which the sentence is true.
The `failed natural deduction by counter-example' technique can be used
to show that P 0 C .
Intended interpretations correspond to extralogical deductions.
Quantier equivalences can be applied to transform sentences.
Natural deduction is sound:
If P ` C then P j= C
Natural deduction is complete:
If P j= C then P ` C
280 Models
18.9 Exercises
1. (a) If A ` B then A j= B (soundness of natural deduction). Hence, if
A 2 B then : : :?
(b) If A 2 B does A j= :B ?
(c) If A j= B does A 2 :B ?
(d) If A 0 B what about A ` :B ?
(e) If A 0 :B does A ` B ?
(f) If fS 1 S 2 : : : Sng j= T is valid does fS 2 : : : Sng 2 T ?
(g) If S is true in no situations then :S is true in every situation.
True or false?
2. Complete the missing cases in the proof of soundness of Natural
Deduction given in Section 18.5.
3. (a) Apply the method used in the completeness proof to derive a model
of the sentences fC ^ N ! T H ^ :S (H ^ :(S _ C )) ! P N :P g.
First convert the sentences to the restricted form using equivalences
and then apply the method.
(b) Find a natural deduction proof of ? from the converted sentences.
4. Show that the following arguments are not valid, that is, the premisses 2
the conclusion. Find two structures in each case in which the premisses
are true but the conclusion false. Try the `failed natural deduction by
counter-example' technique in order to help you to nd the structures:
(a) likes(Mary John) 8x: likes(John x)] 2 :9y: :(likes(Mary y)):
(b) :8x: 8y: Di(x y)^R(x y) ! R(y x)] 2 8u: 8v: Di(u v)^R(u v) !
:R(v u)]:
(c) 8x: F (x) _ G(x)] 2 8x: F (x) _ 8y: G(y):
(d) 9v: F (v) ^ 9u: G(u) 2 9x: F (x) ^ G(x)]:
(e) 8x: 9y: M (x y) 2 9v: 8u: M (u v):
5. For each structure and each set of sentences decide the truth/falsity of
the sentences in the structure:
(a) f8x: R(x x) 8x: 8y: R(x y) ! R(y x)]g Structures:
i. D = fa b cg, R(a b) = R(a c) = R(b c) = R(c b) = tt,
R(a a) = R(b b) = R(c c) = R(b a) = R(c a) = ff
ii. D = f1 2 3 4 : : :g, R is the relation <
iii. D = f1 2 3 : : :g, R is the relation divides(x y)
(b) f8x: 9y: P (x) ! Q(x y)], 9z: P (z), 9z: Q(b z) ! 8u: P (u)]g
Structures:
i. D = f1 2 3 : : :g, b is the number 2, P (x) is the relation x
is even, Q(x y) is the relation divides(x y)
Exercises 281
ii. D = fFred Susan Maryg, b is Mary, P (Fred) = Q(Mary Fred) =
Q(SusanFred) = tt, P (Susan) = P (Mary) = ff , all other pairs for
Q = ff
(c) f9z: 8u: P (f(u) z)g Structures:
i. D = f0 1 ;1 2 ;2 : : :g, P is the relation <, f is the
function: f(u) =j u j
ii. D = f1 2 3 : : :g P is the relation <, f is the successor
function.
6. Find as many dierent models as you can for the sentences:
f8x: 8y: 8z: P (x y z ) ! P (s(x) y s(z))] 8x: P (a x x)g
7. Decide on the truth values of the sentences of Example 18.2 in the
structure with domain= f0
1
2 g and in which A means 0,
P (n) means n 0, and Q(m n) means m2 = n.
8. The completeness proof for propositional sentences given in the text can
be extended to include all logical operators by using the fact that the
following (ND) equivalences can be found:
:(A ^ B ) :A _ :B :(A _ B ) :A ^ :B
:(A ! B ) A ^ :B A ! B :A _ :B ::A A
That is (for example), A ! B ` :A _ B and :A _ B ` A ! B .
(a) Prove each of the above (ND) equivalences.
(b) Once you have proofs of the equivalences they can be used to
rewrite any sentence into ^- _ -: form. The A and B can be any
sentences. In particular, prove that if A ` B , B ` A, A ` B and
0 0
B ` A then
0 0
:A ` :B and :B ` :A
A ^ A ` B ^ B and B ^ B ` A ^ A
0 0 0 0
A _ A ` B _ B and B _ B ` A _ A
0 0 0 0
A ! A ` B ! B and B ! B ` A ! A
0 0 0 0
9. Show that quantiers respect equivalences. That is, if A(a) B (a) for
sentences A and B and some constant a, then 8x: A(x) 8x: B (x) and
9x: A(x) 9x: B (x). (Hint: use induction on the structure of A and
B .)
10. We say that A occurs positively in a sentence F if it is within an even
number (or zero) of negations. It occurs negatively otherwise. Show that,
if A occurs positively in a sentence F and A j= B and replacing A by
B in F gives G, then F j= G. Also, show that if A occurs negatively in
F then G j= F .
Appendix A
Well-founded induction
Well-founded orderings
Suppose we are interested in proving `by induction', that is, using (1)|(3)
above, statements of the form 8x : A: P (x), where A is some set such as nat.
We formalize the idea of simplicity with the notion of well-founded ordering.
Denition A.1 Let A be a set, and < a binary relation on A. < is
a well-founded ordering i every non-empty subset X of A has a minimal
element, that is, some x 2 X such that if y < x then y 62 X .
282
Well-founded induction 283
Note that although < is called an ordering, there is no requirement for it
to be transitive or to have any other of the usual properties of orderings.
Theorem A.2 Let A be a set and < a binary relation on A. Then the
following are equivalent:
1. < is a well-founded ordering.
2. A contains no innite descending chains a1 > a2 > a3 > : : :
(Of course, a > b means b < a.)
3. (Principle of well-founded induction) Let P (x) be a property of elements
of A such that for any a 2 A, if P holds for every b < a then P also
holds for a. Then P holds for every a.
Proof
1 =) 3 (This is really an abstraction of the induction idea presented
informally above. The condition on P is the formalization of the step
nding that the counter-example is not a counter-example.) Let P be a
property as stated, and let X be the set fx 2 A : :P (x)g. If X 6= ?
then by well-foundedness there is a minimal element a in X (`a simplest
counter-example'). For any b < a we have b 62 X , so P (b) holds hence
by the conditions on P we have P (a), which contradicts a 2 X . The
only way out is that X = ?, that is, P (a) for all a.
2 =) 1 Choose a1 2 X (possible, because X 6= ?). If a1 is minimal in X ,
then we are done otherwise, we can nd a1 > a2 2 X . Again, either a2
is minimal or we can nd a2 > a3 2 X . We can iterate this, and it
must eventually give us an element minimal in X , because otherwise we
would obtain an innite descending chain, contradicting (2).
3 =) 2 Let P (x) be the property `there is no innite descending chain
starting with x'. Then P satises the condition of (3), and so P holds
for every a. Hence there are no innite descending chains at all. 2
These three equivalent conditions play dierent conceptual roles. (1), as
in the denition of well-foundedness, is the direct formalization of the ability
to `nd simplest counter-examples'. (2) is usually the most useful way of
checking that some relation < is well-founded, and (3) is the logical principle.
Box proofs
We can put the induction principles into natural deduction boxes. This is
not so much because we want to formalize everything, as to show the proof
obligations, the assumptions and goals when we use induction.
The general principle of well-founded induction, given a set A and a
well-founded ordering <, is shown in Figure A.1.
284 Well-founded induction
a:A 8y : A: (y < a ! P (y )) IH
..
.
P (a)
8x : A: P (x) induction
Figure A.1
The box, with the piece of proof that you have to supply, is the induction
step. The formula labelled (IH) is the induction hypothesis, and it is
a valuable free gift. If it weren't there, then the proof would just be
ordinary 8I introduction and the goal in the box would be more dicult (or
impossible). We shall now look at examples of well-founded orderings, with
their corresponding induction principles.
nat
This is the most basic example. You cannot have an innite descending
sequence of natural numbers, so the ordinary numeric ordering < is
well-founded. Figure A.2 gives the principle of course of values induction:
Figure A.2
A variant on this is obtained by taking < to be not the ordinary numeric
order, but a dierent relation dened by m `<'n if n = m + 1. Then the
induction hypothesis is 8m : nat(n = m + 1 ! P (m)), which works out in two
dierent ways according to the value of n. If n = 0, it is vacuously true |
there are no natural numbers m for which 0 = m + 1. If n > 1, the only
possible m is n ; 1, and so it tells us P (n ; 1). Separating these two cases
out, and in the second case replacing m by n ; 1, we obtain in Figure A.3
the principle of simple induction.
It is no coincidence that these two boxes (the base case and the induction
step) correspond to the two alternatives in the datatype denition for natural
Well-founded induction 285
..
. n : nat P (n)
..
P (0) .
P (n + 1)
8n : nat: P (n) induction
Figure A.3
numbers:
num ::= 0 | suc num
Note two non-examples of well-founded orderings.
1. The integers under numeric <: for there are innite descending chains
such as
0 > ;1 > ;2 > ;3 > : : :
2. The positive rationals under numeric <:
1 > 1=2 > 1=3 > 1=4 > 1=5 > : : :
Recursion variants
Let A be any set, and v : A ! nat any function. Then we can dene a
well-founded ordering < on A by
x < y i v(x) < v(y) (numerically)
The induction principle is given in Figure A.4.
Figure A.4
This is course of values induction `on v'. Plainly nat here could be replaced
by any other set with a well-founded ordering. The programming examples
286 Well-founded induction
had P expressing the correct working of some function f , and it could be put
into the form
P (x) pre(x) ! post(x f (x))
where pre and post together give the specication. v is now the recursion
variant, and the `principle of circular reasoning' comes out (after incorporating
some 8I ) in Figure A.5.
Figure A.5
Lists
For lists xs, ys: !*], we can dene a well-founded order easily enough by
using the length, # (for example, as a recursion variant):
xs < ys i # xs < # ys
However, an interesting alternative is to dene
xs < ys i xs is the tail of ys
This gives the principle of list induction.
..
. h : * t : *] P (t)
..
P (]) .
P (h : t)
8xs : *]: P (xs) induction
Figure A.6
Figure A.6 contains an example of structural induction.
Exercises 287
Pairs and tuples
Theorem A.3 Let A and B be two sets with well-founded orderings. We
shall (naughtily) write the same symbol `<' for both the orderings. Then
A B can be given a well-founded ordering by
(a b) < (a b ) i a < a _ (a = a ^ b < b )
0 0 0 0 0
Proof Suppose there is an innite descending chain (a1 b1) > (a2 b2) >
(a3 b3) > : : : . We have a1 > a2 > a3 > : : : and it follows from the
well-foundedness of a that the ais take only nitely many values as they go
down. Suppose an is the last one, then eventually an = an+1 = an+2 = : : : and
bn > bn+1 > bn+2 > : : : . But this is impossible by well-foundedness on B . 2
This can be extended to well-founded orderings on tuples, and it is really
the same idea as lexicographic (alphabetical) ordering. but note that this
depends critically on the xed length of the tuples. For strings of arbitrary
(though nite) length, lexicographic ordering is not well-founded. For example,
`taxis' > `a1taxis' > `aa1taxis' > `aaa1taxis' > `aaaa1taxis' > : : :
There is a reasoning principle associated with the well-founded orderings on
tuples (see Exercise 2), but perhaps the most common way to exploit the
ordering is by choosing a recursion variant whose value is a tuple instead of
a natural number.
A.1 Exercises
1. Another variant of the principle of course of values induction, shown in
Figure A.2, is obtained by using a well-founded ordering on any subset
of the natural numbers (for example, < on the set of even natural
numbers). Write down the proof obligations using proof boxes for such
a variant.
2. Write down the proof obligations using proof boxes for a reasoning
principle based on a well-founded ordering on tuples.
Appendix B
Summary of equivalences
288
Appendix C
^E , ^I , _E , and _I rules
^E
P1 ^ : : : ^ Pn
Pi (^E )
for each of Pi , i = 1 n.
^I
... ...
P1 Pn
P1 ^ : : : ^ Pn (^I )
_E
P1 ::: Pn
P1 _ : : : _ Pn ... ...
C C
C (_E )
_I
Pi
P1 _ : : : _ Pn (_I )
for each of Pi , i = 1 n
289
290 Summary of natural deduction rules
!I , !E , :I , :E and :: rules
!I
P
...
Q
P !Q (!I )
!E
P P !Q
Q (!E )
:I
P
...
?
:P (:I )
:E
P :P
? (:E )
::
::Q
Q (::)
Equality rules
eqsub
a = b S a]
S b] (eqsub)
where S a] means a sentence S with one or more occurrences of a
identied and S b] means those occurrences replaced by b.
reex
a = a (reex)
Summary of natural deduction rules 291
Universal quantier rules
8E
8x: P x]
P t] (8E )
where t occurs in the current context.
typed 8E
is-type(t) 8x : type: P x]
P t] (8E )
8I
c8I
...
P c]
8x: P x] (8I)
where c must be new to the current context.
typed 8I
c8I is-t(c)
...
P c]
8x : t: P x] (8I )
8!E and 8:E
8x: P x] ! Qx]] P c] and 8x: :P x] P c]
Qc] (8!E ) ? (8:E )
292 Summary of natural deduction rules
Existential quantier rules
9I
P b]
9x: P x] (9I )
where b occurs in the current context.
typed 9I
is-type(b) P b]
9x : type: P x] (9I )
9E
9x: P x]
c9E P c]
...
Q
Q (9E )
where c is new to the current context.
typed 9E
9x : t: P x]
c9E P c]
is-t(c)
...
Q
Q (9E )
Further reading
293
Index