Functional C
Functional C
Functional C
January 3, 1999
i
Functional C
Revision: 6.7
ii
To Marijke
Pieter
Revision: 6.7
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Preface
Familiarise the reader with the syntax and semantics of ISO-C, especially the
power of the language (at the same time stressing that power can kill). We
visit all dark alleys of C, from void * to pointer arithmetic and assignments
in expressions. On occasions, we use other languages (like C++ and Pascal)
to illustrate concepts of imperative languages that are not present in C. C has
been chosen because it is a de facto standard for imperative programming,
and because its low level nature nicely contrasts with SML. Those who want
to learn, for example, Modula-2 or Ada-95 afterwards should not find many
difficulties.
iii
iv Preface
The language of mathematics is used to specify the problems. This includes the
basics of set theory and logic. The student should have some familiarity with the
calculi of sets, predicate logic, and propositional logic. This material is taught at
most universities during a first course on discrete mathematics or formal logic.
The appropriate algorithm is given in SML. SML is freely available for a range
of platforms (PC’s, UNIX work stations, Apple), and is therefore popular as a teach-
ing language. As many functional languages are not too different from SML, an
appendix gives a brief review of SML for those familiar with any of the other main
stream functional languages, such as Miranda, Haskell, Clean, or Scheme.
As the target language to implement solutions in an imperative style we have
chosen C. The choice to use C and not C++ was a difficult one. Both languages
are mainstream languages, and would therefore be suitable as the target language.
We have chosen C because it more clearly exposes the low level programming. To
illustrate this consider the mechanisms that the languages provide for call by refer-
ence. In C, arguments must be explicitly passed as a pointer. The caller must pass
the address, the callee must dereference the pointer. This in contrast with the call by
reference mechanism of C++ (and Pascal and Modula-2). This explicit call by ref-
erence is a didactical asset as it clearly exposes the model behind call by reference,
and its dangers (in the form of unwanted aliases).
Revision: 6.8
Preface v
As this book is intended to be used in a first year course, only few assumptions
were made about prior knowledge of the students. Reasoning about the correct-
ness of programs requires proof skills, which students might not have acquired at
this stage. Therefore we have confined all proofs to specially marked exercises.
To distinguish the programming exercises from the exercises requiring a proof, we
have marked the latter with an asterisk. We are confident that the book can be
used without making a single proof. However we would recommend the students
to go through the proofs on a second reading. The answers to one third of the ex-
ercises are provided in Appendix A.
The student should have an understanding of the basic principles of comput-
ing. This would include base 2 arithmetic and the principles of operation of the
von Neumann machine. A computer appreciation course would be most appro-
priate to cover this material. The book contains examples from other areas of com-
puter science, including data bases, computer graphics, the theory of program-
ming languages, and computer architecture. These examples can be understood
without prior knowledge of these areas.
Acknowledgements
The help and comments of Hugh Glaser, Andy Gravell, Laura Lafave, Denis Nicole,
Peter Sestoft, and the anonymous referees have been important to us. The mate-
rial of the book has undergone its first test in Southampton in 1995/1996. The first
year Computer Science students of 1995, and in particular Jason Datt and Alex
Walker have given us a lot of useful feedback.
We have used a number of public domain software tools in the development
of the book. The noweb literate programming tools of Norman Ramsey, the rail
road diagramming tools from L. Rooijakkers, gpic by Brian Kernighan, TEX, LATEX,
New Jersey SML, and the Gnu C compiler were particularly useful.
Revision: 6.8
vi Preface
Revision: 6.8
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Contents
Preface iii
1 Introduction 1
1.1 The functional and the imperative paradigms . . . . . . . . . . . . . . 1
1.1.1 The advantage of state . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.2 The advantage of pure functions . . . . . . . . . . . . . . . . . 3
1.1.3 Idiomatic building blocks in C . . . . . . . . . . . . . . . . . . 3
1.2 Guide to the book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
vii
viii CONTENTS
3 Loops 51
3.1 A model of the store . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.2 Local variable declarations and assignments . . . . . . . . . . . . . . 53
3.3 While loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.3.1 Single argument tail recursion . . . . . . . . . . . . . . . . . . 61
3.3.2 Multiple argument tail recursion . . . . . . . . . . . . . . . . . 64
3.3.3 Non-tail recursion: factorial . . . . . . . . . . . . . . . . . . . . 68
3.3.4 More on assignments . . . . . . . . . . . . . . . . . . . . . . . . 73
3.3.5 Breaking out of while-loops . . . . . . . . . . . . . . . . . . . . 75
3.4 For loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.4.1 Factorial using a for-loop . . . . . . . . . . . . . . . . . . . . . 83
3.4.2 Folding from the right . . . . . . . . . . . . . . . . . . . . . . . 86
3.5 Generalizing loops and control structures . . . . . . . . . . . . . . . . 88
3.5.1 Combining foldl with map: sum of squares . . . . . . . . . . 89
3.5.2 Combining foldl with filter: perfect numbers . . . . . . . 91
3.5.3 Nested for statements . . . . . . . . . . . . . . . . . . . . . . . 95
3.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.7 Further exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5 Arrays 133
5.1 Sequences as a model of linear data structures . . . . . . . . . . . . . 134
5.1.1 The length of a sequence . . . . . . . . . . . . . . . . . . . . . . 134
5.1.2 Accessing an element of a sequence . . . . . . . . . . . . . . . 134
5.1.3 Updating an element of a sequence . . . . . . . . . . . . . . . 135
5.1.4 The concatenation of two sequences . . . . . . . . . . . . . . . 135
5.1.5 The subsequence . . . . . . . . . . . . . . . . . . . . . . . . . . 136
5.2 Sequences as arrays in SML . . . . . . . . . . . . . . . . . . . . . . . . 136
5.2.1 Creating an SML array . . . . . . . . . . . . . . . . . . . . . . . 136
Revision: 6.8
CONTENTS ix
6 Lists 181
6.1 Lists of characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.1.1 List access functions: head and tail . . . . . . . . . . . . . . . . 183
6.2 The length of a list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
6.3 Accessing an arbitrary element of a list . . . . . . . . . . . . . . . . . . 186
6.4 Append, filter and map: recursive versions . . . . . . . . . . . . . . . 186
6.4.1 Appending two lists . . . . . . . . . . . . . . . . . . . . . . . . 186
6.4.2 Filtering elements from a list . . . . . . . . . . . . . . . . . . . 188
6.4.3 Mapping a function over a list . . . . . . . . . . . . . . . . . . 190
6.5 Open lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
6.5.1 Open lists by remembering the last cell . . . . . . . . . . . . . 193
6.5.2 Open lists by using pointers to pointers . . . . . . . . . . . . . 195
6.5.3 Append using open lists . . . . . . . . . . . . . . . . . . . . . . 197
6.6 Lists versus arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
6.6.1 Converting an array to a list . . . . . . . . . . . . . . . . . . . . 199
6.6.2 From a list to an array . . . . . . . . . . . . . . . . . . . . . . . 201
6.7 Variable number of arguments . . . . . . . . . . . . . . . . . . . . . . 204
6.8 Store reuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
6.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
6.10 Further exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Revision: 6.8
x CONTENTS
7 Streams 213
7.1 Counting sentences: stream basics . . . . . . . . . . . . . . . . . . . . 214
7.1.1 Efficiently transferring a stream to a list . . . . . . . . . . . . . 216
7.1.2 Avoiding the intermediate list . . . . . . . . . . . . . . . . . . . 218
7.1.3 IO in C: opening files as streams . . . . . . . . . . . . . . . . . 219
7.2 Mean sentence length: how to avoid state . . . . . . . . . . . . . . . . 222
7.3 Counting words: how to limit the size of the state . . . . . . . . . . . 224
7.3.1 Using a sliding queue . . . . . . . . . . . . . . . . . . . . . . . 225
7.3.2 Implementing the sliding queue in SML . . . . . . . . . . . . . 226
7.3.3 Implementing the sliding queue in C . . . . . . . . . . . . . . 228
7.3.4 Counting words using arrays . . . . . . . . . . . . . . . . . . . 231
7.4 Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
7.4.1 Quicksort on the basis of lists . . . . . . . . . . . . . . . . . . . 235
7.4.2 Quicksort on the basis of arrays . . . . . . . . . . . . . . . . . . 236
7.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
7.6 Further exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
8 Modules 245
8.1 Modules in C: files and the C preprocessor . . . . . . . . . . . . . . . 246
8.1.1 Simple modules, #include . . . . . . . . . . . . . . . . . . . . 246
8.1.2 Type checking across modules . . . . . . . . . . . . . . . . . . 250
8.1.3 Double imports . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
8.1.4 Modules without an implementation . . . . . . . . . . . . . . 253
8.1.5 The macro semantics of #define directives . . . . . . . . . . 253
8.2 Compiling modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
8.2.1 Separate compilation under UNIX . . . . . . . . . . . . . . . . 255
8.2.2 Separate compilation on other systems . . . . . . . . . . . . . 263
8.3 Global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
8.3.1 Random number generation, how to use a global variable . . 263
8.3.2 Moving state out of modules . . . . . . . . . . . . . . . . . . . 266
8.3.3 Scoping and life time of global and local variables . . . . . . . 268
8.4 Abstract Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
8.5 Polymorphic typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
8.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
8.7 Further exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Revision: 6.8
CONTENTS xi
Revision: 6.8
xii CONTENTS
Revision: 6.8
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 1
Introduction
1
2 Chapter 1. Introduction
Revision: 6.19
1.1. The functional and the imperative paradigms 3
Both paradigms have their advantages. The imperative paradigm makes it eas-
ier to deal with state, as the state does not have to be communicated from one
function to the other, it is always present. The functional paradigm allows us to
create building blocks that can be used more freely. We will elaborate on these
issues below.
Revision: 6.19
4 Chapter 1. Introduction
This is the aim of this book: we wish to create highly idiomatic and efficient C
code, but also wish to create good building blocks, preserving all the techniques
that are common knowledge in the world of functional languages. Examples in-
clude pure functions, polymorphic functions, currying, algebraic data types and
recursion.
Revision: 6.19
1.2. Guide to the book 5
worked out, it designs a system for device independent graphics. There are large
parts left to be implemented by the reader. The third study develops an imple-
mentation for a simple graphics language. The algorithms are outlined, and the
data structures are sketched, the implementation of this case is left to the reader.
Appendix A contains the answers to a selection of the exercises. The exercises
give readers the opportunity to test and improve their skills. There are two types
of exercises. The normal exercises reinforce the general skills of problem solving.
Many of them require an implementation of some SML code, and almost all of
them require the implementation of some C functions. The exercises marked with
an asterisk are targeted at readers who are interested in the fundamental issues of
programming. All proofs of the theorems that we pose are left as an ‘exercise ’ to
the reader.
Appendix B is a brief review of SML for people familiar with other functional
languages. It suffices that people can read SML programs. We only discuss the
subset of SML that we use in the book and only in terms of other functional lan-
guages. This appendix also discusses the (small set of) SML library functions that
we use.
Appendix C lists the library functions of C. All programming languages come
with a collection of primitive operators and data types as well as a collection of
library modules providing further facilities. C is no exception and has a large set of
libraries. We present a small number of functions that are present in the C library.
The last Appendix D gives the complete syntax of ISO-C using railroad dia-
grams. These are intuitively clearer than the alternative BNF notation for syntax.
The syntax diagrams are intended as a reference.
This book is not a complete reference manual. The reader will thus find it use-
ful to be able to refer to the ISO-C reference manual [7]. It contains all the details
that an experienced C programmer eventually will have to master and that an in-
troductory text such as this one does not provide.
Revision: 6.19
6 Chapter 1. Introduction
Revision: 6.19
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 2
7
8 Chapter 2. Functions and numbers
tasks more quickly and more accurately than the human brain. A computational
model is a basic understanding of the kind of things that a computer can do and
how it does them. We will formulate two such models, one that is applicable when
we write SML programs and another that applies when we write C programs.
The definition of the named function is looked up in the list of known func-
tion definitions.
✁
The formal arguments of the function definition are associated with the val-
ues provided by the actual arguments of the function.
✁
The body of the function is examined to yield a further expression that can be
evaluated now. This includes evaluation of the arguments, where necessary.
✁
As soon as all subsidiary expressions are evaluated, the result of the function
is returned. The value of this result only depends on the values of the actual
arguments of the function.
The mechanism of starting with a given expression, activating the function it men-
tions, and looking for the next expression to evaluate comprises the computational
model that underlies an implementation of SML. Knowledge of this mechanism
enables the programmer to reason about the steps taken whilst the program is be-
ing executed. The programmer will have to make sure that only a finite number of
such steps are required, for otherwise the program would never yield an answer.
In most circumstances, the programmer will also try to make sure that the least
number of steps are used to find an answer, as each step takes a certain amount of
time.
Revision: 6.47
2.1. A model of computation 9
Revision: 6.47
10 Chapter 2. Functions and numbers
This statement returns no useful value; its sole purpose is to print the text
Hello world as a side effect. The second statement is:
return 0 ;
The execution of this statement results in the value 0 to be delivered as the value of
the expression main( ). The main program above shows that the body of a func-
tion in C consists of a number of statements. The computational model prescribes
that these statements are obeyed in the order in which they are written.
Revision: 6.47
2.2. Elementary functions 11
The symbolic name stands for the name of the function to be captured.
Any concrete function name may be substituted for , for example euclid.
✎
Revision: 6.47
12 Chapter 2. Functions and numbers
The C version of the function schema, which corresponds exactly to the function
schema in SML above, is:
/*C function schema*/
✁ ✝ ( ✁ ✂ ✟ ✂ , ... ✁ ☎ ✟ ☎ ) {
if ( ☞ ) {
return ✌ ;
} else {
return ✍ ;
}
}
The symbols in this schema are to be interpreted as above, with the proviso that
the various syntactic differences between SML and C have to be taken care of in a
somewhat ad-hoc fashion.
✁
Identifiers in SML may contain certain characters that are not permitted in
C. These characters must be changed consistently. The requirements for C
identifiers are discussed later on page 13.
✁
The basic types in SML are generally the same as in C, but some must be
changed. For example, real in SML becomes double in C. A list of basic
types is given at the end of this chapter.
✁
Many of the operators that appear in SML programs may also be used in C
programs. The operators of C are discussed in Section 2.2.4
✁
Let us now apply the function schema to the function euclid. Below is a ta-
ble of correspondence which relates the schema, the SML program and the C pro-
gram. The first column of the table contains the relevant symbolic names from the
schema. In the second column, we put the expressions and symbols from the SML
function. The third column contains a transformation of each of the elements of
the SML function into C syntax.
Schema Functional C
: euclid euclid
✁✄✂ : int int
✁✁ : int int
✁✞✝ : int int
✟ ✂ : m m
✟ : n n
☞ : n > 0 n > 0
✌ : euclid n (m mod n) euclid( n, m%n )
✍ : m m
Revision: 6.47
2.2. Elementary functions 13
The creation of the C version of euclid is now simply a matter of gathering the
information from the third column of the table and substituting that information
in the right places in the C function schema. This yields:
int euclid( int m, int n ) {
if( n>0 ) {
return euclid( n, m%n ) ;
} else {
return m ;
}
}
The process of transforming an SML function into C is laborious but not difficult.
We shall often tacitly assume that all the steps in the transformation have been
made and just present the net result of the transformation.
The euclid example has been chosen because there is a C implementation that
is close to the mathematical and the functional versions. The C implementation of
euclid is shown below, embedded in a complete program.
#include <stdio.h>
Revision: 6.47
14 Chapter 2. Functions and numbers
This directive tells the compiler to include the text from the file stdio.h at this
point. This is necessary for the program to be able to use most input and output
facilities. The include directive and many others are discussed in Chapter 8 (on
modules); for now this directive will just be used in every program that is written.
After the include directive, two functions are defined: euclid and main.
Each of the functions consists of a function header, defining the type of the func-
tion result and the name and type of the function arguments, and a function body,
enclosed in curly brackets, defining the behaviour of the function. The function
headers, the function body, and the execution of this C program are discussed in
turn below.
Revision: 6.47
2.2. Elementary functions 15
that the compiler can check the user specified types against its own inferred types.
Any mismatch points out errors in the program. In C, the programmer must spec-
ify all types.
✁
} else {
}
✁
This can be read as follows: if the conditional expression evaluates to true, exe-
☞
cute the statements , else execute the statements . We generally use upper case
italic letters to indicate where statements may occur. Upon completion of or ,
✁
any statements following the if statement are executed. In the case of euclid,
there are no statements following the if statement. The if statement above can
also be used without the optional else part:
if( ☞ ) {
}
If the else part is omitted, the C program will just continue with the next state-
ment if the conditional expression evaluates to false. The curly brackets { and }
☞
are used to group statements. They can be omitted if there is only one statement
between them, but we will always use them to make it easier to modify programs
later on.
Expressions in C are like functional expressions except that they use a slightly
different syntax. For example, the arguments of a function must be separated
by commas, and they must be enclosed in brackets. Therefore the function ap-
plication f x y in the functional language is written in C as f(x,y). The op-
erators have also different syntax: for example, ✟ ✒✦✥★✄✧ ✂
is denoted in C as x%y.
Revision: 6.47
16 Chapter 2. Functions and numbers
Most other operators have their usual meaning (a full list is given shortly in Sec-
tion 2.2.4). This information is sufficient to interpret the body of the function
euclid:
{
if( n>0 ) {
return euclid( n, m%n ) ;
} else {
return m ;
}
}
If n is greater than zero, execute the then part:
return euclid( n, m%n ) ;
Else, if n is not greater than zero, execute the else part:
return m ;
The first of these two statements orders the function to return, more specifically to
deliver, the value of the expression euclid( n, m%n ) as the function’s return
value. The second of the statements orders to return the value of m (in this case,
the value of n equals 0).
The keyword else of the if statement in this particular function is redundant.
Consider the two statements:
{
if( n>0 ) {
return euclid( n, m%n ) ;
}
return m ;
}
The first of these, the if statement, will execute the statement below if n is greater
than zero:
return euclid( n, m%n ) ;
If the return is executed, the function is terminated without executing any more
statements. Thus the second statement, return m, is only executed if n is equal
to zero. We will come back to redundant else statements in the next section.
The body of the main function contains 4 statements. The first statement is:
printf( "%d\n", euclid( 14, 12 ) ) ;
It is a call to the function printf. This call has two arguments: the first argument
is a string "%d\n", and the second argument is the value that is returned after
calling euclid with arguments 14 and 12. As we will see shortly, this call to
printf will print the value of euclid( 14, 12 )
The next two statements in the body of main are similar to the first: the sec-
ond statement prints the greatest common divisor of the numbers 14 and 11, and
the third prints the greatest common divisor of the numbers 558 and 198. The
last statement returns the value 0 to the calling environment of the program. This
issue will be discussed in more detail in the next paragraphs.
Revision: 6.47
2.2. Elementary functions 17
Revision: 6.47
18 Chapter 2. Functions and numbers
This completes the execution of the first statement of main. Upon completion of
this first statement, the next statement of main is executed. Again this is a call to
printf. The first argument of this function is a constant string, and the next ar-
gument is again a call to the function euclid, this time with arguments 14 and
11. Euclid is invoked with m equal to 14 and n equal to 11. Instead of giving an-
other lengthy explanation of which function calls which function, a trace of func-
tion calls can be given as follows:
euclid( 14, 11 ) calls
euclid( 11, 3 ) calls
euclid( 3, 2 ) calls
euclid( 2, 1 ) calls
euclid( 1, 0 ) is
1
The chain of calls will return the value 1, which is printed by the main program.
Exercise 2.1 How many times is the function euclid called when executing the
third statement of main?
2.2.4 Integers
The euclid example works with integer numbers, which are captured by the C
type int. There are many ways to denote integer constants in C. The most com-
mon form is to use the decimal representation, as already used before. 13 stands
for the integer 13. A minus sign can be used to denote negative values, as in -13.
A second option is to specify numbers with a different base, either base 8 or base
16. Hexadecimal values (using base 16) can be specified by writing them after the
letters 0x. So 0x2C and 44 both refer to the same number. Octal numbers (with
base 8) should begin with the digit 0. So 0377, 0xFF and 255 all specify the same
number.
The operators that can be applied to integers are listed below, together with
their SML and mathematical equivalents:
C SML Math Meaning
+ Unary plus Unary
- ˜ Unary minus
˜ ✆ Bitwise complement
*
/
*
div, / div ,
✁ Multiplication
Division
Binary
>> ✟ ✟
Revision: 6.47
2.2. Elementary functions 19
There is one group of operators that does not have an SML equivalent: the bit
operators. Bit operations use the two’s-complement binary interpretation of the
integer values. The ˜ operator inverts all bits, the &, | and ˆ operators perform a
bit-wise and, or, and exclusive-or operation on two bit patterns. The following are
all true:
(˜0) == -1,
(12 & 6) == 4,
(12 | 6) == 14,
(12 ˆ 6) == 10
The << and >> operations shift the bit pattern a number of positions: x<<i shifts
x by i positions to the left, and y>>j shifts y by j positions to the right. Some
examples:
(12 << 6) == 768,
(400 >> 4) == 25
Bit operations can be supported efficiently because all modern computer systems
store integers bitwise in two’s-complement form.
The type int is only one of a multitude of C types that can be used to manipu-
late integer values. First of all, C supports the unsigned int type. The keyword
unsigned implies that values of this type cannot be negative. The most impor-
tant feature of the unsigned int type is that the arithmetic is guaranteed to be
modulo-arithmetic. That is, if the answer of an operation does not fit in the domain,
the answer modulo some big power of 2 is used. Repeatedly adding one to an
unsigned int on a machine with 32-bit integers will give the series , , , . . .
✁ ✁ ✁ ✁ ✁
✪ ✝
longer arithmetic (128-bit numbers, for example) is needed. Other types of inte-
gers will be shown shortly, in Section 2.3.3.
C provides the three usual logical operators. They take integers as their operands,
and produce an integer result:
Revision: 6.47
20 Chapter 2. Functions and numbers
Revision: 6.47
2.3. Characters, pattern matching, partial functions 21
The keyword typedef associates a type with a typename, bool in this case.
The type can be any of the builtin types (int for example), or user constructed
types such as enum { false=0, true=1 }. The keyword enum is used to de-
fine an enumerated type. The domain spans two elements in this case, false and
true, where false has the numeric value 0 and true has the numeric value 1.
The general form of an enumerated type is:
enum { ✂✁ , ✆✂ , ... }
The ✂✁ , ✆✂ , , ✏✕✏✒✏ are the identifiers of the domain. The identifiers are represented
as integers. The identifiers can be bound to specific integer values, as was shown
in the definition of the type bool. The combination of typedef with enum has a
counterpart in SML:
/*C combination of typedef and enum*/
typedef enum { ✄✁ , ✆✂ , ... } ✁ ;
(*SML scalar datatype declaration*)
datatype ✁ = ✄✁ | ✆✂ | ... ;
The following C fragment defines an enumerated type language:
typedef enum { Japanese, Spanish, Chinese } language ;
This is equivalent to the following SML declaration:
datatype language = Japanese | Spanish | Chinese ;
for operator precedence state that multiplication takes precedence over addition,
as indicated in the specification below. Here, the set ☎ represents all possible char-
acters.
✍ ✝✆
✍ ✂ ☎
✟ ✂
+ * ☎
Revision: 6.47
22 Chapter 2. Functions and numbers
✆ ✁ ✟ ✆
✟ + ✂ + ✟ ✂ ✂ ✂
✆ ✁ ✆ ✆ ✟
✟ + ✂ * ✟ ✂ ✂
✆ ✁ ✆ ✟ ✆
✟ * ✂ + ✟ ✂ ✂
✆ ✁ ✆ ✟ ✆ ✆
✟ * ✂ * ✟ ✂
Revision: 6.47
2.3. Characters, pattern matching, partial functions 23
function eval is a partial function, a function which is undefined for some values
of its arguments.
The version of eval that uses the conditionals can be transformed into a C
program using a schema in the same way as the function euclid was translated
using a schema. However, we need a new schema, since the eval function con-
tains a cascade of conditionals, whereas the function schema of Section 2.2 sup-
ports only a single conditional. The development of this schema is the subject of
Exercise 2.2 below. For now, we will just give the C version of eval.
In C, the type char is the type that encompasses all character values. Charac-
ter constants are written between single quotes. The code below is incomplete as
we are yet unable to deal with /*raise Match*/. We will postpone giving the
complete solution until Section 2.3.2, when we are satisfied with the structure of
eval.
int eval( int x, char o1, int y, char o2, int z ) {
if( o1 == + && o2 == + ) {
return (x + y) + z ;
} else {
if( o1 == + && o2 == * ) {
return x + (y * z) ;
} else {
if( o1 == * && o2 == + ) {
return (x * y) + z ;
} else {
if( o1 == * && o2 == * ) {
return (x * y) * z ;
} else {
/*raise Match*/
}
}
}
}
}
The else-parts of the first three if statements all contain only one statement,
which is again an if statement. This is a common structure when implementing
pattern matching. It becomes slightly unreadable with all the curly brackets and
the ever growing indentation, which is the reason why we drop the curly brackets
around these if statements and indent it flatly:
int eval( int x, char o1, int y, char o2, int z ) {
if( o1 == + && o2 == + ) {
return (x + y) + z ;
} else if( o1 == + && o2 == * ) {
return x + (y * z) ;
} else if( o1 == * && o2 == + ) {
return (x * y) + z ;
} else if( o1 == * && o2 == * ) {
Revision: 6.47
24 Chapter 2. Functions and numbers
return (x * y) * z ;
} else {
/*raise Match*/
}
}
This indicates a chain of choices which are tried in order. The final else-branch is
reached only if none of the previous conditions are true.
Exercise 2.2 The function schema of Section 2.2 supports only a single conditional.
Generalise this schema to support a cascade of conditionals, as used by the
last SML and C versions of eval.
Exercise 2.3 Give the table of correspondence for eval, using the function schema
of Exercise 2.2.
As discussed before, the else’s are redundant in this case because each branch
returns immediately. Reconciling this yields another equivalent program:
int eval( int x, char o1, int y, char o2, int z ) {
if( o1 == + && o2 == + ) {
return (x + y) + z ;
}
if( o1 == + && o2 == * ) {
return x + (y * z) ;
}
if( o1 == * && o2 == + ) {
return (x * y) + z ;
}
if( o1 == * && o2 == * ) {
return (x * y) * z ;
}
/*raise Match*/
}
During the execution of eval in the form above, the four if statements will be exe-
cuted one by one until one of the if statements matches. It is a matter of taste which
of the two forms, the else if constructions or the consecutive if statements, to
choose. The first form is more elegant since it does not rely on the return state-
ment terminating the function. Therefore we will use that form.
The chain of if statements is not efficient. Suppose that o1 is a* , and o2
is a+ . The first two if statements fail (because o1 is not a+ ). The third if
statement succeeds, so the value of (x * y) + z will be returned. There are two
tests on o1: it is compared with the character+ twice, whereas one test should
be sufficient. To remove this second test from the code, the program has to be
restructured. First, one has to test on the first operator. If it is a+ , nested if
statements will be used to determine which of the two alternatives to evaluate.
Similarly, one if statement is needed to check if the first operator is a* . In this
Revision: 6.47
2.3. Characters, pattern matching, partial functions 25
situation, nested if statements will determine which of the remaining two alterna-
tives to choose. This gives the following definition of eval:
int eval( int x, char o1, int y, char o2, int z ) {
if( o1 == + ) {
if( o2 == + ) {
return (x + y) + z ;
} else if( o2 == * ) {
return x + (y * z) ;
} else {
/*raise Match*/
}
} else if( o1 == * ) {
if( o2 == + ) {
return (x * y) + z ;
} else if( o2 == * ) {
return (x * y) * z ;
} else {
/*raise Match*/
}
} else {
/*raise Match*/
}
}
Exercise 2.4 Can you devise a translation schema between the pattern matching
SML code and the C code that uses a nested if-then-else? If not, what is the
problem?
The above program has a functional equivalent, which is not as nice to read, even
though we have introduced two auxiliary functions xymul and xyadd to improve
the legibility:
(* eval : int -> char -> int -> char -> int -> int *)
fun eval x o1 y o2 z
= if o1 = "+"
then xyadd x y o2 z
else if o1 = "*"
then xymul x y o2 z : int
else raise Match
(* xyadd : int -> int -> char -> int -> int *)
and xyadd x y o2 z
= if o2 = "+"
then x + y + z
else if o2 = "*"
then x + y * z : int
Revision: 6.47
26 Chapter 2. Functions and numbers
(* xymul : int -> int -> char -> int -> int *)
and xymul x y o2 z
= if o2 = "+"
then x * y + z
else if o2 = "*"
then x * y * z : int
else raise Match ;
It should be the task of the compiler to replace pattern matching with a suitable
if-structure. As C does not support pattern matching, it is the task of the C pro-
grammer to design an efficient statement structure in the first place.
Revision: 6.47
2.3. Characters, pattern matching, partial functions 27
Although the type char is a separate type, it is actually part of the family of
integers. In C, characters are encoded by their integer equivalent. This means that
the characterq and the integer value 113 are actually identical (assuming that an
ASCII encoding of characters is used). Denoting an argument to be of type char
means that it is a small integer constant. The type char covers all integers that are
necessary to encode the character set.
The fact that the characters are a subset of the integers has a number of con-
sequences. Firstly, functions that convert characters into integers and vice versa,
such as the chr and ord of SML and other languages, are unnecessary. Secondly,
all integer operators are applicable to characters. This can be useful as shown by
Revision: 6.47
28 Chapter 2. Functions and numbers
Exercise 2.5 Given that a machine encodes the characterq with 113 and the
character0 with 48, what will the output of the following program be?
int main( void ) {
char c0 = 0 ;
int i0 = 0 ;
char cq = 113 ;
int iq = 113 ;
printf("%c = %d, %c = %d\n", c0, i0, cq, iq ) ;
return 0 ;
}
Additionally, two standard functions toupper and tolower are provided that
map a lower case letter to the upper case equivalent, and an upper case letter to
Revision: 6.47
2.4. Real numbers 29
the lower case equivalent respectively. To use any of these functions, one must
include the file ctype.h using the include directive:
#include <ctype.h>
The ins and outs of the include statement are reserved for Chapter 8. An example
of the use of these functions is a function that converts a lowercase character to
an uppercase letter, and an uppercase character to a lowercase. Here is the SML
version:
(* makeupper : char -> char *)
fun makeupper c
= if "a" <= c andalso c <= "z"
then chr (ord c - ord "a" + ord "A")
else if "A" <= c andalso c <= "Z"
then chr (ord c - ord "A" + ord "a")
else c ;
Note that the implementation assumes that both the upper and lower case letters
are ordered contiguously. The C version replaces the tests on the characters with
some of the standard predicates, but the structure of the function is the same as in
SML:
char makeupper( char c ) {
if( islower( c ) ) {
return toupper( c ) ;
} else if( isupper( c ) ) {
return tolower( c ) ;
} else {
return c ;
}
}
✁ ✞✁
(2.3)
✟✆✠ ✂
Revision: 6.47
30 Chapter 2. Functions and numbers
✆ ✆ ✄✁ ✂ ✆ ✁✖✆ ☎✟
✁
✂ ☎✄ ✁ ✂ ☎✆ ✁
✄ ✄ ✄ ✄ ✄ ✄
div ✁
div
There are multiplications which are grouped into two groups of div multipli-
☞ ☞ ✝
cations, and, if is odd, one single multiplication. This group of div multipli-
☞ ☞ ✝
cations yields by definition div , which gives the following recursive definition
✁
✂☎
✍
☞
✝✞
✟ ✆ ✍ if ✫✪ ☞
✞✠ ☛✡
if is odd (2.4)
✂
✁ div ✟ ✍
☞
sqr ✁
otherwise
✁ ✟ ✆
where sqr ✟ ✟ ✟
This reads as follows: any number to the power 0 equals 1; any number to the
power an-odd-number is equal to the number times the number to the power the-
odd-number-minus-1; and any number to the power an-even-number equals the
square of the number to the power the-even-number-divided-by-2.
Exercise 2.6 The original definition of (2.3) would require 127 multiplications ✁
✌☞
to calculate✂
Exercise 2.7 Prove by induction on that the two definitions of power, (2.4) and☞
Equation (2.4), defining power and its auxiliary functions square and odd,
would be formulated in SML using conditionals as shown below:
(* square : real -> real *)
fun square x : real = x * x ;
Revision: 6.47
2.4. Real numbers 31
The following table gives the answers when applying the function power to a
sample set of arguments:
power 3.0 0 = 1.0 ;
power 5.0 4 = 625.0 ;
power 1.037155 19 = 1.99999 ;
The C equivalents of these functions and three example computations are shown
below as a complete C program. The general function schema of Exercise 2.2 has
been used to transform the SML functions into C.
#include <stdio.h>
Revision: 6.47
32 Chapter 2. Functions and numbers
print a floating point style number. To accomplish this, the format %f must occur
in the format string. The function printf does not check whether the types in the
format string correspond with the types of the arguments. If printf is asked to
print something as a floating point number (with a %f format) but is supplied an
integer argument, then the program will print in the best case an arbitrary number
and in the worst case will crash.
The execution of the power program is similar to the execution of the previous
programs. It starts in main, which in turn calls power, before calling printf to
print the first result.
Exercise 2.8 How many times are the functions square and power called when
executing the third statement of main?
Revision: 6.47
2.4. Real numbers 33
When interpreted as a C expression, the result will be the double 3.0. The di-
vision operator is applied to two integers, hence the result of the division is the
integer 2. Since one of the operands of the addition is a double, 1.0, the integer 2
will be coerced into the double 2.0 so that the result of the expression as a whole
will be the double 3.0. When interpreted as an SML expression, the same type
error arises as in the previous case.
1 + 5/2
As before, the operator / applied to two integers will be interpreted in C as integer
division. Thus, the result of the integer division is the integer 2. The operands of
the addition will both be integers, so that the value of the expression as a whole
will be the integer 3. When interpreted as an SML expression, the same type error
arises as in the previous two cases, this time it can be resolved by using a div
operator.
Internally, floating point numbers are stored in two parts: the fraction (or man-
✁ ✂
☞
tissa) and the exponent. If these are and , the value of a floating point number
✝ ✠ is stored as mantissa ✝ ✠ and
☞ ✆
exponent . Only a limited number of bits are available to store the exponent
and the mantissa. Let us consider, as an example, the layout of a floating point
number in a word of only 8 bits. We have opted for a three bit exponent and a five
bit mantissa. This arrangement is schematical (the real representation is different,
consult a book on computer architecture for full details):
exponent
✂ ✁
☎✄ ✝mantissa
☞ ✆ ☞ ☞ ☞
✂
☞
The bits are numbered such that the least significant bit is the rightmost bit of each
of the mantissa and the exponent.
We assume that the exponent stores two’s-complement numbers. The bit pat-
✝✄
tern for ✞✆
is thus ✄✆ . The mantissa is arranged such that the most significant bit
✂
☞ ☞
counts the halves, the next bit counts the quarter, and so on. Therefore, the
bit pattern for ✝ ✠ is ✆
✪ ✏ . The representation of
✪ ✪ ✪ ✝ ✠ as an 8-bit floating point ✪ ✏
✆
✪ ✪ ✪
its precision. A consequence of the limited precision of the mantissa is that the
floating point numbers are a subset of the real numbers. Hence, two floating point
numbers can be found in C, say x and y, such that x does not equal y, and there
does not exist a floating point number between these two numbers. As an exam-
ple, in one C implementation there is no floating point number between:
1.000000000000000222044604925031
and:
1.000000000000000444089209850062
Revision: 6.47
34 Chapter 2. Functions and numbers
or between:
345678912.0000000596046447753906
and:
345678912.0000001192092895507812
The floating point numbers actually step through the real numbers, albeit in small
steps. The actual step size depends on the machine and compiler. This stepping
behaviour has a number of consequences. Firstly, many numbers cannot be rep-
resented exactly. As an example, many computer systems cannot represent the
number 0.1 exactly. For example, the nearest numbers are:
0.0999999999999999916733273153113
and:
0.1000000000000000055511151231257
For this reason floating point numbers should be manipulated with care: 0.1*10
is not necessarily equal to 1.0. On some systems, it is greater than 1; on others,
it is less than 1. An equality test on floating point numbers, for example a==0.1,
will probably not give the expected result either.
2.5.1 Sums
A common operation in mathematics, statistics, and even in everyday life is the
summation of a progression of numbers. We learn at an early age that the sum of
an arithmetic progression is:
☎ ✎ ✆ ✁✌✎ ✟
✞✂ ✝ ✂ ✂
✂ ✏✒✏✒✏
✎ ✂ (2.5)
✟✆✠
✂ ✝
Another useful sum, which is not so well known, adds successive odd numbers.
This sum can be used to calculate the square of a number:
☎
✁ ✆✴✎ ✟ ✁ ✆ ✟ ✎
✂ ✂
✂ ✠ ✂ ✏✒✏✒✏ ✝ ✝ (2.6)
✟ ✠
✂
It is interesting to look at the differences and similarities of these sums.✎ The differ-
ences are that they may have other begin and end values ✎
than and (although
in our two examples both summations range from to ), and that they may have
Revision: 6.47
2.5. Functions as arguments 35
different expressions with . The similarity is that the value of ranges over a
progression of numbers, thus producing a sequence of expressions, each of which
uses a different value of . The values of these expressions are added to produce
the end result. The commonality in these patterns of computation can be captured
in a higher order function. The differences in these patterns of computation are
captured in the arguments of the higher order function. We meet here an applica-
tion of the important principle of procedural abstraction: look for common aspects
of behaviour and capture them in a procedure or function, whilst allowing for flex-
ibility by using arguments. The sum function shown below should be regarded as
the SML equivalent of the mathematical operator : ☛
(* sum : int -> int -> (int -> real) -> real *)
fun sum i n f = if i > n
then 0.0
else f i + sum (i+1) n f ;
The function sum takes as arguments a begin value (argument i) and an end value
(argument n). Both of these are integers. As its third argument, we supply a func-
tion f which takes an integer (from the progression) and produces a real value to
be summed.
The sum of an arithmetic progression is computed by the SML function
terminal below. It uses sum to do the summation. The actual version of f is the
function int2real. It is a function that converts an integer into a real number, so
that the types of the function sum and its arguments are consistent.
(* terminal : int -> real *)
fun terminal n = let
fun int2real i = real i
in
sum 1 n int2real
end ;
To see that this function does indeed compute the sum of an arithmetic progres-
sion we could try it out with some sample values of n. A better way to gain un-
derstanding of the mechanisms involved is to expand the definition of sum by sub-
stituting the function int2real for f. This yields:
(* terminal : int -> real *)
fun terminal n
= let
fun int2real i = real i
fun sum i n = if i > n
then 0.0
else int2real i + sum (i+1) n
in
sum 1 n
end ;
As an example, we can now ‘manually’ evaluate terminal 3. This requires a
series of steps to calculate the answer 6, according to the computational model for
Revision: 6.47
36 Chapter 2. Functions and numbers
SML:
terminal 3 sum 1 3
int2real 1 + sum (1+1) 3
1.0 + sum 2 3
1.0 + ( int2real 2 + sum (2+1) 3 )
1.0 + ( 2.0 + sum 3 3 )
1.0 + ( 2.0 + ( int2real 3 + sum (3+1) 3 ) )
1.0 + ( 2.0 + ( 3.0 + sum 4 3 ) )
1.0 + ( 2.0 + ( 3.0 + 0.0 ) )
6.0
After these preparations, we are now ready to translate our functions sum and
terminal into C. Here is the C version of the sum function:
double sum( int i, int n, double (*f) ( int ) ) {
if( i > n ) {
return 0.0 ;
} else {
return f( i ) + sum( i+1, n, f ) ;
}
}
The sum function uses the function f as its third argument with the following syn-
tax:
double (*f)( int )
This means that the argument f is itself a function which will return a double
value and which requires an integer value as an argument. (Strictly speaking f is a
pointer to a function, for now the difference can be ignored; pointers are discussed
in Chapter 4) The argument f is used by sum as a function to calculate the value of
f( i ). The general syntax for declaring a functional argument of a function is:
✁✞✝ (* )( ✁ ✂ , ... ✁✞☎ )
✍
Here ✁✆✝ gives the type of the return value, and ✁ ✂ ✏✒✏✒✏✞✁✞☎ are the types of the argu-
✍
ments of the function. (In turn, each of ✁✕✂ ✏✒✏✒✏✓✁✆☎ , can be a function type.) The SML
equivalent of the type of this higher order function is:
(✁ ✂ -> ... ✁ ☎ -> ✁ ✝ )
A type synonym can be defined for function types using the typedef mechanism
of C. As an example, define:
typedef double (*int_to_double_funcs)( int ) ;
This represents a type with the name int_to_double_funcs which encom-
passes all functions that have an int as an argument and result in a double value.
This type synonym would have been declared in SML as:
type int_to_real_funcs = int -> real ;
Revision: 6.47
2.5. Functions as arguments 37
Exercise 2.9 Give the table of correspondence for the transformation of the SML
version of sum into the C version. Use the function schema from the begin-
ning of this chapter.
The transformation of terminal into C poses a slight problem. C does not al-
low to define local functions, like the function int2real. Instead, int2real has
to be defined before terminal. It would have been better if the auxiliary func-
tion int2real could have been declared locally to terminal, as it is only used
within that function. Most other programming languages do permit this, Pascal or
Modula-2 for example.
double int2real( int i ) {
return (double) i ; /* return i would suffice */
}
formula:
✂ ✝
✁ ✞ ✂
✂ ✠ ✝
✂ ✂
✏✕✏✒✏ (2.7)
2.5.2 Products
The functions used in the previous section were all defined in terms of sums. Sim-
ilar functions can be given in terms of products. Here is the product function in
SML:
(* product : int -> int -> (int -> real) -> real *)
fun product i n f = if i > n
then 1.0
else f i * product (i+1) n f ;
Revision: 6.47
38 Chapter 2. Functions and numbers
With this new function product, we can define the factorial function in the
same way as terminal was defined using sum:
(* factorial : int -> real *)
fun factorial n = let
fun int2real i = real i
in
product 1 n int2real
end ;
Exercise 2.12 Give the C versions of the product and factorial functions
above.
✏✒✏✕✏ (2.8)
✂✁ ✄✁ ✁
✂ ✂ ✂ ✂
The sum and product functions are actually similar. Besides having a different
name, The product function differs from the sum function in only two aspects:
2. The product function uses the unit element 1.0 of the multiplication where
sum uses the unit element 0.0 of the addition.
The sum and product functions are so similar that it seems a good idea to try
to generalise further. The common behaviour of sum and product is a repeti-
tive application of some function. This repeated behaviour requires a base value
(0.0 for sum and 1.0 for product) and a method of combining results (+ for sum
and * for product). The common behaviour can be captured in a rather heavily
parametrised SML function repeat:
(* repeat : a -> (b -> a -> a) ->
int -> int -> (int -> b) -> a *)
fun repeat base combine i n f
= if i > n
then base
else combine (f i) (repeat base combine (i+1) n f) ;
Revision: 6.47
2.5. Functions as arguments 39
The repeat function has two functional arguments, combine and f. The best
way to understand how it works is by giving an example using repeat. Let us
redefine the function sum in terms of repeat:
(* sum : int -> int -> (int -> real) -> real *)
fun sum i n f
= let
fun add x y = x + y
in
repeat 0.0 add i n f
end ;
A succinct way to characterise the way in which repeat has been specialised to
sum is by observing that the following functions are equivalent:
sum = repeat 0.0 add
We have omitted the arguments i, n, and f as they are the same on both sides.
With repeat, we have at our disposal a higher order function which captures the
repetitive behaviour of sum, product, and many more functions. The repeat
function derives its power from the wide range of functions and values that can
be supplied for its five arguments.
Exercise 2.14 Redefine the SML function product in terms of repeat.
Exercise 2.15 Transform repeat and the redefined version of sum into C func-
tions.
✏ ☎ ✪
computed as follows:
✂ ✝
✞✂ ✂ ✂
(2.9)
✂✁✞✝
✝✠✟☛✡ ✡ ✡
✂
Revision: 6.47
40 Chapter 2. Functions and numbers
of data. In this example, we use bisection to find the root of a function, the ✟ -value
for which the function value is .✪
Before we present the details of the bisection method, let us have a look at its ✁ ✟
low) and ✍ ( ✍ for high). These two points form the boundaries of an interval of
✁
the X-axis which contains the root. The interval is rather large so it is not a good
approximation to the root. The smaller we can make the interval, the better the
approximation to the root becomes. The bisection method calculates a series of
approximations, with each subsequent one being better than the previous. The
process is stopped when we are sufficiently close to the root.
X-axis
✁ ✟
✟
✁
✍ ✁
When given two initial boundaries and ✍ , the bisection method calculates a
☞
✁ ✁
new point (for mid) exactly between and ✍ . It then checks whether the root
✁
☞
✁
resides to the left or to the right of the point . If the root resides to the left of
☞
, the right half of the original interval between and ✍ is abandoned, and the
✁ ✁
is half the size of the original interval. If the root resides to the right of , the left ☞
half is abandoned, and the process continues with the interval between ✖✂ and
✍ . This interval is also half the size of the original interval. The process of halv-
✁
ing the current interval continues until it is deemed small enough to serve as an
approximation to the root. Here is a graphical rendering of the halving process:
Revision: 6.47
2.5. Functions as arguments 41
Interval 1
Interval 2
Interval 3
Int. 4
X-axis
✁ ✟
✟
✆
✁ ✍ ✍ ✂ ✍ ✁
Summarising, when given some initial estimates and , the bisection method
✁✌☞✞✍✏✎✑✟
✁ ✁
✍
finds two points and ✁ , which are as near as possible to either side of the inter-
☎
✍
tinuous function, there must be a root of between and . The bisection method ✂ ☎✄
☞
✍
✁✶☞✠✟
Given a particular set of values , there are three possibilities:✡ ✡ ✍
If ✪ , the root has been found (or at least we are close enough to the
root).
✁
If
✌✁ ☞✠✟ is positive, we need to move left and continue to use ☞ as the new
value of .
✁✶☞ ✟ ☞
✍
☞ ✍ ✢ ✶✁ ☞ ✟ ✢
✝ ✝ ✝
bisection
☞ ✍ if ✢ ✢
✝✞ ✆
✝✞
✁✍ ✟
✞
if
bisection (2.10)
bisection ✁ ✍✏☞✠✟✏✍
✍ ✡
✟✆
✞
if
✍ ✞
✞✠
✍ ☛
bisection otherwise
where
☞ ✂ ✍
To express that ✞ we are close enough to the root, the bisection method uses two
✢ ✁✌☞✠✟ ✢
margins, ✆ and . We say that we are close enough to a root if either the function
enough,
✢
result is close to 0,✞
✍
✢
✠✆ , or if the root is between two bounds that are close
. Below, this is visualised:
✡
✡
Revision: 6.47
42 Chapter 2. Functions and numbers
We can choose and so that the root is calculated with sufficient precision. Note
✆
that there is a problem if they are both set to zero. Regardless of how close and ✍
are, they will never actually meet. Each step brings them closer by halving the size
of the interval. Only infinitely many steps could bring them together. Secondly, as
was shown in the previous section, computers work with limited precision arith-
metic only. This might cause unexpected problems with the implementation of
floating point numbers. By accepting a certain error margin, both these problems
are avoided.
The above version of the bisection method requires to be an increasing func-
✁ ✟ ✁ ✟
of and . Making one of them (or both) larger reduces the accuracy of the method
✆
but also causes it to find an approximation to the root faster. Conversely, making
the values smaller slows down the process but makes it more accurate.
(* eps,delta : real *)
val eps = 0.001 ;
val delta = 0.0001 ;
The function bisection below follows the structure of the mathematics. To al-
low for some flexibility, we have given bisection three arguments. The first ar-
gument is the function f, whose root we are looking for. The other two arguments
are the real bounds l and h that represent the current low and high bounds of the
interval in which the root should be found.
(* bisection : (real->real) -> real -> real -> real *)
fun bisection f l h
= let
val m = (l + h) / 2.0
val f_m = f m
in
if absolute f_m < eps
then m
else if absolute(h-l) < delta
then m
else if f_m < 0.0
then bisection f m h
else bisection f l m
Revision: 6.47
2.5. Functions as arguments 43
end ;
The local declarations for m and f_m ensure that no calculation is performed more
than once. This makes the bisection function more efficient than a literal trans-
lation of the mathematics. The function absolute is defined as follows:
(* absolute : real -> real *)
fun absolute x = if x >= 0.0
then x
else ˜x ;
As an example of using the bisection function, let us calculate the root of the
parabola function below.
(* parabola : real -> real *)
fun parabola x = x * x - 2.0 ;
The following table gives the roots of the parabola function when calcu-
lated with different initial bounds. The chosen value of eps is 0.001,
and the chosen value for delta is 0.0001. In each of these cases,
parabola l < 0 < parabola h.
bisection parabola 1.0 2.0 = 1.41406 ;
bisection parabola 0.0 200.0 = 1.41449 ;
bisection parabola 1.2 1.5 = 1.41445 ;
✁ ✁
The three answers are all close to the real answer ✏ ✠✆☎ ✏✒✏✒✏ . The answers
✂
✝ ✝
are not exactly the same because bisection computes an approximation to the
answer.
A complete C program implementing and using the bisection method is given
below:
#include <stdio.h>
Revision: 6.47
44 Chapter 2. Functions and numbers
2.6 Summary
The following C constructs were introduced:
Revision: 6.47
2.6. Summary 45
/*constant declarations*/
/*statements*/
}
Each function contains declarations of local values, and a series of state-
ments. When the function is called, the local constants will first be evalu-
ated (in the order in which they are written), whereupon the statements are
executed, in the order specified.
✄
Constant declarations A constant with type and value is declared as follows:
✁✄
✁
const = ; ✁
Statements Three forms of statements were discussed, the return statement, the
if statement and the function-call statement. A return statement has the fol-
lowing form:
return ;
It will terminate the execution of the current function, and return the value
of the expression as the return value of the function. The type of the expres-
sion should match the result type of the function. An if statement comes in
two flavours, one with two branches, and one with only a then-branch:
if( ) {☞
if( ) { ☞
} else { }
✁
}
Depending on the value of the conditional expression , the first or the sec- ☞
ond (if present) series of statements, or , will be executed. The third state-
✁
Printing output The function printf requires a string as its first argument, print-
ing it while substituting %-abbreviations with the subsequent arguments as
follows:
Revision: 6.47
46 Chapter 2. Functions and numbers
Expressions Expressions in C are like SML expressions, except that some of the
operators are slightly different, as listed in Sections 2.2.4 and 2.2.5. Expres-
sions are typed, but there are fewer types than usual: booleans and charac-
ters are represented by integers.
Types The following table relates C types to their SML and mathematical equiva-
lents:
C type
int
SML
int
✂✁
Mathematics
✍ ✍ ✍ ✍
char ☎
✗ a b ✏✕✏✒!
✏ @ ✏✕✏✒✏ ✯
✂ ✝
double real
Extra care has to be used when using characters (which are actually integers,
page 27). Booleans are also represented by integers and must be defined ex-
plicitly, as shown in Section 2.2.6.
Naming types : A name can be associated with a type using the keyword
typedef:
typedef ✁ ;
This will bind the identifier to the type ✁ . For some types, most notably
function types, must appear in the middle of ✁ :
typedef ✁✆✝ (* )(✁ ✂ , ... ✁✆☎ ) ;
This defines a function type with name identical to the SML type:
type = ✁ ✂ -> ... ✁✆☎ -> ✁✆✝ ;
Main Each C program must have a main function. The function with the name
main is the function that is called when the program is executed.
The most important programming principles that we have addressed in this chap-
ter are:
✁
Revision: 6.47
2.7. Further exercises 47
If possible a function should be pure. That is, the function result should
depend only on the value of its arguments. The systematic transformation
method introduced in this chapter guarantees that all C functions are indeed
pure.
✁
A function should be total, that is, it should cover all its cases. The SML com-
piler will usually detect if a function is partially defined. In C, a runtime
error might occur or the program might return a random result, if the func-
tion is incomplete. By first writing a function in SML, we obtain appropri-
ate warnings from the compiler, and if all such warnings are taken to heart,
our C programs will not have this problem. The language C does not have
built-in support for pattern matching, but a chain of if statements can be
employed to simulate pattern matching.
✁
One vitally important issue that we have not addressed in this chapter is the effi-
ciency of the C implementations. This is the subject of the next chapters.
✝ ✠
degrees Centigrade.
Exercise 2.21 Some computers offer a ‘population count’ instruction to count the
number of bits in an integer that are set to 1; for example, the population
count of 7 is 3 and the population count of 8 is 1.
(a) Give a specification of the pop_count function. ✎ Assume that the
given word ✟ is represented as a sequence✍ of bits as follows
✂
Revision: 6.47
48 Chapter 2. Functions and numbers
Exercise 2.22 A nibble is a group of four adjacent bits. A checksum of a word can
be calculated by adding the nibbles of a word together. For example, the
checksum of 17 is 2 and the checksum of 18 is 3.
(a) Give a specification of the checksum function. Assume that a given
✎✂✁ ✎✂✁ ✟ is✎ represented
word ✎ as a sequence of ✎ nibbles as follows ✂
✪ ✏✕✏✒✏ .
✡
Write an SML function fib to calculate the -th Fibonacci number. Then,
give the corresponding C function and a main✎ program to test the C version
of fib for at least three interesting values of .
Exercise 2.24 The ‘nFib’ number is a slight variation on the Fibonacci number. It
is defined as follows:
✎✷✍ ✂☎
☎
✁
✂
✍ ✎
☎ ✂
☎ ✡
✂ ✂
☎ ✡
if ☞ ✝
Revision: 6.47
2.7. Further exercises 49
(a) What is the difference between the Fibonacci series and the nFib se-
ries?
(b) Write an SML function nfib to calculate the -th nFib number.
✎
(c) Give the C function that corresponds exactly to the SML version of
nfib.
(d) Write a main function that calls nfib and prints its result. Test the C
version of nfib for at least three interesting values of .
✎
(e) If you study the formula for ☎ above closely, you will note that the
value ☎ is the same as the number of function calls made by your SML
or your C function to calculate ☎ . Measure how long it takes SML to
✆
compute and calculate the number of function calls per second for
✁
SML.
(f) Perform the same measurement with your C program. Which of the
two language implementations is faster? By how much? Document
every aspect of your findings, so that someone else could repeat and
✎
corroborate your findings.
Exercise 2.25 Write an SML function power_of_power to compute the -th term
in the following series:
☎ ✁✄✂
✍ ☞✝✍ ☞ ✍ ☞✁ ✄✂✆☎ ✍ ☞✁ ✞✝✟✂ ✂✡✠ ☎ ✏✒✏✒✏ ☎ ✁✆✂
✄
☎ ☎ ✁✄✂
✆
☎ ☎
✠
☎
✠
✎ ☞
The thus counts the number of occurrences of in the term. Give the C
version of power_of_power and give☞ a main ✎ program to test the C version
for at least three interesting values of and .
✟ ✂ ✟
☞☛ ✁ ✟ ✟
✟
✟ ✟
(2.12)
☛✁ ✟ ✁✟
✁
tangent intersects the X-axis, ✟✘✂ , is closer to the root of the function; ✟✘✂ is
✁ ✟
therefore a better approximation of the root. The process is repeated using
✟✡✂ . Drawing the tangent of ✟ ✂ and intersecting it with the X-axis gives
the point ✟ , which is closer to the root of the function than ✟✎✂ . The process
can be repeated until the function value becomes (almost) zero.
Revision: 6.47
50 Chapter 2. Functions and numbers
X-axis
✟
✁ ✟
✟ ✟✡✂ ✟ ✁
✂
✪ ✠
Revision: 6.47
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 3
Loops
51
52 Chapter 3. Loops
A: ✏✕✏✒✏
B: ✏✕✏✒✏
C: ✏✕✏✒✏
D: ✏✕✏✒✏
The store of the computer is accessed mainly through the arguments and the local
variables of the functions (we will discuss global variables in Chapter 8). Unless
essential, we will not differentiate between arguments and local variables, and we
will simply refer to both as the arguments, since the values that are carried by the
local variables must be stored somewhere, just like the arguments. It is the task of
the compiler to allocate cells for the arguments that occur in programs.
The number of available cells is related to the amount of storage that is phys-
ically present in the computer. Therefore, a realistic model of the store will limit
the number of available cells. Storage and thus cells are often in short supply, and
using store impacts performance. The compiler will need to do its best to allocate
arguments to cells in such a way that cells are reused as often as possible.
Consider instructing the computer directly, as is common with pocket calcula-
tors. Firstly, store four numbers, arbitrarily chosen as 17, 3, 1, and 1000, in the cells,
thus allocating one cell to each number:
A: ✪✚✪ ✝
B: ✪✚✪ ✪
✂
C: ✪✚✪ ✪
D: ✪ ✪✚✪
Revision: 6.41
3.2. Local variable declarations and assignments 53
To add all the numbers together, start by adding 17 and 3. Then add 1 to the result,
and so on. Where would the intermediate sums, 20 and 21, and the final result
(1021) be stored? All cells are in use and there is no free cell. The solution is to
reuse cells after they have served their useful life. One possibility would be to use
cell “A” for this purpose:
The simple model of the store shown above is adequate for our purposes. It can be
refined in several ways; the interested reader should consult a book on computer
architecture [14, 2]. Here, the simple model of the store will be used to investigate
the behaviour of a number of C functions.
value of the cell associated with . The value should have type . The local
✟ ✁
Revision: 6.41
54 Chapter 3. Loops
An assignment statement can be used to change the value of the cell associ-
ated with a local variable. Assignment statements have the general form:
✟ = ;
Here is an identifier and should be an expression of the same type as .
✟ ✟
The function main below uses local variable declarations and assignments to im-
plement a C program that adds the contents of the four cells as described in the
previous section. The function main first declares four appropriately initialised
local variables, A, B, C and D. It then uses three assignment statements to add the
values. The cell associated with the local variable A is used to store the intermedi-
ate and final results. The program finally prints the sum as stored in A and returns
to the calling environment.
int main( void ) {
int A = 17 ;
int B = 3 ;
int C = 1 ;
int D = 1000 ;
A = A + B ;
A = A + C ;
A = A + D ;
printf( "%d\n", A ) ;
return 0 ;
}
We will now investigate how we can use the model of the store to study the be-
haviour of a C program in detail. We do this by making an execution trace. An
execution trace is a step by step account of the execution of a program, showing
the values associated with the variables involved in the execution.
Execution begins with a call to main. This is shown in Step 1 below. The cells
associated with A, B, C and D are also allocated at Step 1. The rendering of the
store is associated with the position in the program that has been reached by the
execution. We draw a box representing the store with an arrow pointing at the
position in the program that has been reached.
Revision: 6.41
3.2. Local variable declarations and assignments 55
At Step 2 the store is updated by the first assignment because the value associated
with A changes from 17 to 20:
int main( void ) {
int A = 17 ;
int B = 3 ;
int C = 1 ;
int D = 1000 ; A : 20
A = A + B ; B:3
(Step 2)
A = A + C ; C:1
A = A + D ; D : 1000
printf( "%d\n", A ) ;
return 0 ;
}
At Step 3 the store is updated again to reflect the effect of the second assignment
statement:
int main( void ) {
int A = 17 ;
int B = 3 ;
int C = 1 ;
int D = 1000 ;
A = A + B ; A : 21
A = A + C ; B:3
(Step 3)
A = A + D ; C:1
printf( "%d\n", A ) ; D : 1000
return 0 ;
}
The last assignment statement updates the store again to yield the final association
of the value 1021 with the variable A:
Revision: 6.41
56 Chapter 3. Loops
The printf statement at Step 5 prints the value 1021 associated with A. The
printf statement does not alter the contents of the store, so we will not redraw
the box that displays the contents:
int main( void ) {
int A = 17 ;
int B = 3 ;
int C = 1 ;
int D = 1000 ;
A = A + B ;
A = A + C ;
A = A + D ;
printf( "%d\n", A ) ; (Step 5)
return 0 ;
}
The function main finally returns to its caller delivering the return value 0. The
entry return 0 in the store box below indicates that the return value needs to
be communicated from the callee (the function main) to the caller. The latter may
need this value for further computations. In this particular case the caller is the
operating system, which interprets a return value of 0 as successful execution of
the program, and any other value as an indication of a failure.
Revision: 6.41
3.3. While loops 57
A cell can only be reused if the cell is not going to be accessed anymore. Therefore
the order of events is important. The programmer should be able to control that
order so as to guarantee the appropriate sequencing of events. In the case of the
example above, it would not be possible to start adding 1000 to 1 while still using
cell A for intermediate results. This would destroy the value 17 before it is used.
The reuse of cell A destroys the old value associated with that cell.
One must be careful not to destroy a value that is needed later, as it will in-
troduce an error. One of the major differences between functional programming
and imperative programming is that functional programmers cannot make such
errors, for they have no direct control over store reuse.
✂ ☎ ✡ ✂ ☎ ✂ ☎✝✢
any year after 1582 is given by the specification:
✂
✁✟
leap
✒ ✗ ✛ ✬ ✁✁ ✁ ✦✒ ✥★✧ ✫✪ ✟ ✟ ✁
✬ ✒✦✥★✧ ✪ ✪ ✫✪ ✒✦✥★✧ ✪ ✪ ✪ ✟ ✟ ✯
✂✁
leap (3.1)
✁
✂ ☞ ✂
The condition for a leap year is a bit too long for this example, so we simplify it
only to give the right answer between 1901 and 2099 (the general case is left to the
✂ ☎✲✡ ✂ ☎
reader, see Exercises 3.2 and 3.4):
leap
leap
✁✟
✂ ✒ ✗ ✛ ✂ ☎✣✢
✄✁
☞ ✂ ✬ ✁ ✒✦✥★✧ ✮✪ ✟ ✯
✁
(3.2)
Given a particular year , a leap year cannot be far away. The following algorithm
✂
moves onto the next year if the current year is not a leap year.
(* leap : int -> int *)
fun leap y = if y mod 4 <> 0
then leap (y+1)
else y ;
Revision: 6.41
58 Chapter 3. Loops
The technique that has been developed in the previous chapter to translate from
an SML function to a C function is directly applicable:
int leap( int y ) {
if( y % 4 != 0 ) {
return leap( y+1 ) ;
} else {
return y ;
}
}
Exercise 3.2 Modify the SML function leap to deal with any year after 1582 using
(3.1) and transform the SML function into C.
When this function is executed as part of a program, the function leap associates
a cell with the argument y. The cell is initialised and used, but it is not reused. To
see this, we will look at the execution trace of leap. Execution begins with a call
to leap with some suitable argument value, say 1998. This is Step 1 below. The
argument y is also allocated at Step 1.
int leap( int y ) { y : 1998 (Step 1)
if( y % 4 != 0 ) {
return leap( y+1 ) ;
} else {
return y ;
}
}
At Step 2 the test on y is performed, which yields true. This implies that a new call
must be made to leap.
int leap( int y ) {
if( y % 4 != 0 ) {
return leap( y+1 ) ; (Step 2)
} else {
return y ;
}
}
At Step 3 the function leap is entered recursively. The store is extended with the
cell necessary to hold the new argument. The old argument is kept for later use
as y . This signals that, until the new call terminates, the cell referred to as y
is inaccessible: there is no identifier in the program with this name, and it is not
even a legal identifier in C. The store is used as a stack of cells, with only the most
recently stacked cell being accessible. There are now 2 cells in use; the new cell
stacked on top of the old one:
Revision: 6.41
3.3. While loops 59
At Step 4 the test on the new value of y is performed, yielding true again. The
store remains unaltered, and it has therefore not been redrawn:
int leap( int y ) {
if( y % 4 != 0 ) {
return leap( y+1 ) ; (Step 4)
} else {
return y ;
}
}
At Step 5 the function leap is entered for the third time. This extends the store
with a third cell. The previous values of y are now shown as y and y . Only y
is accessible. y : 2000
int leap( int y ) { y : 1999 (Step 5)
if( y % 4 != 0 ) { y : 1998
return leap( y+1 ) ;
} else {
return y ;
}
}
At Step 6 the test on the latest value of y is performed, yielding false, because 2000
is divisible by 4. The store remains the same:
int leap( int y ) {
if( y % 4 != 0 ) { (Step 6)
return leap( y+1 ) ;
} else {
return y ;
}
}
At Step 7 the current call to leap reaches the else-branch of the conditional. The
store remains unaltered.
Revision: 6.41
60 Chapter 3. Loops
At Step 8 the third call to leap terminates, returning the value 2000 to the previ-
ous call. In returning, the cell allocated to the argument of the third call is freed.
There are now two cells in use, of which only y is accessible.
int leap( int y ) {
if( y % 4 != 0 ) { return 2000
return leap( y+1 ) ; y : 1999 (Step 8)
} else { y : 1998
return y ;
}
}
At Step 10 the initial call to leap returns the desired result, 2000. This also frees
the last cell y in use by leap.
int leap( int y ) {
if( y % 4 != 0 ) {
return leap( y+1 ) ; return 2000 (Step 10)
} else {
return y ;
}
}
The execution trace of leap(1998) shows how arguments are allocated and re-
membered during calls. The trace also shows the return values of the functions
involved in the trace. The trace of leap does not show that arguments always
have to be remembered. In fact, the leap function does not require arguments to
be remembered when the calls are made. The function leap is said to be tail re-
cursive: a function is tail recursive if the last expression to be evaluated is a call to
the function itself. To contrast tail recursive with non-tail recursive functions, here
Revision: 6.41
3.3. While loops 61
}
The purpose of the while-statement is to evaluate the expression ☞ , and if this
yields true, to execute the statement(s) of the body. The evaluation of ☞ and
the execution of the body are repeated until the condition yields false. The body
should be constructed such that executing it should eventually lead to the con-
dition failing and the while-loop halting. As in the case of the if-statements, the
curly brackets are not necessary if the body of the while-loop consists of just one
statement. We will always use brackets though, to make the programs more main-
tainable.
We now show a transformation for turning a tail recursive function such as
leap into a C function using a while-statement. Consider the following schematic
rendering of a tail recursive function with a single argument:
(*SML single argument tail recursion schema*)
(* : ✁ -> ✁✆✝ *)
fun ✠✟
= if ☞
then ✌
else ✍ ;
Revision: 6.41
62 Chapter 3. Loops
Here the ✟ is an argument of type ✁ . The ☞ , ✌ , and ✍ represent expressions that may
contain occurrences of ✟ . The type of the expression ☞ must be bool, the type of ✌
must be ✁ , and the type of ✍ must be ✁✖✝ . No calls to must appear either directly or
indirectly in any of the expressions ☞ , ✌ , and ✍ . (Such cases will be discussed later
in this chapter.)
A function that can be expressed in the above form of the single argument tail
recursion schema can be implemented efficiently using a C function with a while-
statement as shown schematically below:
/*C single argument while-schema*/
✁✞✝ ( ✁ ✟ ) {
while( ☞ ) {
✟ = ✌ ;
}
return ✍ ;
}
The crucial statement is the assignment-statement ✟ =✌ . This is a command that ex-
plicitly alters the value associated with a cell in the store. When executed, the
assignment-statement computes the value of ✌ , and updates the cell associated
with the argument ✟ . If ✟ occurs in ✌ , the value of ✟ immediately before the as-
signment is used in the computation of ✌ . After the update, the previous value is
lost. The new value is used during the next iteration, both by the condition and by
the assignment-statement. This amounts to the reuse of a store cell as suggested
by the leap example.
Substitution of the schematic values from the table above into the single argu-
ment while-schema yields an iterative C version of leap:
int leap( int y ) {
while( y % 4 != 0 ) {
y = y+1 ;
}
return y ;
}
Exercise 3.3 Show the correspondence between the SML and C versions of leap.
Exercise 3.4 Modify the C function leap to deal with any year after 1582 using
(3.1).
The C version of leap uses the constructs that the typical C programmer would
use. We were able to construct this by using a systematic transformation from a
relatively straightforward SML function. This is interesting as some would con-
sider the iterative C version of leap as advanced. This is because the argument
y is reused as the result during the while-loop. To write such code requires the
programmer to be sure that the original value of the argument will not be needed
later.
Revision: 6.41
3.3. While loops 63
An execution trace of this new version of leap is now in order firstly to see
what exactly the while-statement does and secondly to make sure that only a sin-
gle cell is needed. At Step 1 the iterative version of leap is called with the same
sample argument 1998 as before:
int leap( int y ) { y : 1998 (Step 1)
while( y % 4 != 0 ) {
y = y+1 ;
}
return y ;
}
At Step 4 the condition is evaluated for the second time. The value associated with
y has changed, but its value, 1999, is still not divisible by 4. The condition thus
returns true again.
int leap( int y ) {
while( y % 4 != 0 ) { (Step 4)
y = y+1 ;
}
return y ;
}
At Step 5 the statement y=y+1; is executed for the second time. This changes the
value associated with y to 2000. Again, no new cell is required and y remains
accessible.
Revision: 6.41
64 Chapter 3. Loops
At Step 6 the condition is evaluated for the third time, yielding false because 2000
is divisible by 4. This terminates the loop and gives control to the statement fol-
lowing the while-statement, which is the return statement. Thus the body of the
while-statement y=y+1; is not executed again.
int leap( int y ) {
while( y % 4 != 0 ) {
y = y+1 ;
} (Step 6)
return y ;
}
At Step 7 the function leap terminates: the statement following the while-
statement is executed, returning the value 2000.
int leap( int y ) {
while( y % 4 != 0 ) {
y = y+1 ;
}
return y ; return 2000 (Step 7)
}
The execution trace shows that only one cell is ever used. It also shows that the
conditional of the while-statement is evaluated three times. The body of the while-
statement y=y+1; is executed only twice. This is a general observation: if, for
✎ ✎
✎
✂ times, the body of the
while-statement is executed times.
We have developed an efficient and idiomatic C implementation of the leap
function on the basis of a systematic transformation, the while-schema. The while-
schema is of limited use because it cannot deal with functions that have more than
one argument. In the next section, the while-schema will be appropriately gener-
alised.
Revision: 6.41
3.3. While loops 65
tion with more than one argument in the form below. We will call this tupling of
arguments the pre-processing of the multiple argument while-schema.
(*SML multiple argument tail recursion schema*)
(* : (✁ ✂ * ... ✁ ☎ ) -> ✁ ✝ *)
fun ( ✟✡✂ , ... ✟☛☎ )
= if ☞
then ✌
else ✍ ;
Here, ✟ ✂☛✏✒✏✒✏✄✟✔☎ are the arguments of types ✁ ✂✗✏✒✏✕✏✖✁✞☎ respectively. The expressions ☞ ,
✌ , and ✍ are now dependent on ✟✘✂✑✏✒✏✒✏✓✟✔☎ . No calls to , either directly or indirectly,
must appear in any of the expressions ☞ , ✌ , and ✍ .
It would have been nice if the corresponding generalisation of the while-
schema could look like this.
/*C multiple argument while-schema*/
✁✞✝ ( ✁✄✂ ✟✡✂ , ... ✁✆☎ ✟✔☎ ) {
while( ☞ ) {
(✟ ✂ , ... ✟✔☎ ) = ✌ ;
}
return ✍ ;
}
Unfortunately the multiple assignment-statement is not supported by C:
( ✟✡✂ , ... ✟✔☎ ) = ✌ ; /* incorrect C */
In one of the predecessors to the C language, BCPL, the multiple assignment was
syntactically correct. It is an unfortunate fact of life that useful concepts from an
earlier language are sometimes lost in a later language. Multiple assignment is in
C achieved by a number of separate single assignment-statements. Therefore to
produce a correct C function, we must do some post-processing. The transforma-
tion of a multiple assignment into a sequence of assignments is shown in the two
examples below. The first example is an algorithm for computing the modulus of
two numbers. The second example uses Euclid’s algorithm to compute the great-
est common divisor of two numbers.
Independent assignments
The function modulus as shown below uses repeated subtraction for computing
the modulus of m with respect to n, where m and n both represent positive natural
numbers.
(* modulus : int -> int -> int *)
fun modulus m n = if m >= n
then modulus (m - n) n
else m : int ;
This function has been written using a curried style, which is preferred by some
because curried functions can be partially applied. C does not offer curried func-
tions so we will need to transform the curried version of modulus into an uncur-
Revision: 6.41
66 Chapter 3. Loops
ried version first. This requires the arguments to be grouped into a tuple as fol-
lows:
(* modulus : int*int -> int *)
fun modulus (m,n) = if m >= n
then modulus (m - n,n)
else m : int ;
If you prefer to write functions directly with tupled arguments, you can save your-
self some work because the preparation above is then unnecessary.The correspon-
dence between the elements of the multiple argument while-schema and the ver-
sion of modulus with the tupled arguments is as follows:
schema: Functional C
✎
: modulus modulus
: 2 2
(✁✄✂ *✁✁ ): (int*int) (int,int)
✁✞✝ : int int
( ✟✡✂ , ✟ ): (m,n) (m,n)
☞ : m >= n m >= n
✌ : (m - n,n) (m - n,n)
✍ : m : int m
(m,n) = (m - n,n) ; m = m - n ;
n = n ;
The assignment-statement n=n; does nothing useful so we can safely remove it.
This yields the following C implementation of modulus:
int modulus( int m, int n ) {
while( m >= n ) {
m = m - n ;
}
return m ;
}
Revision: 6.41
3.3. While loops 67
With the aid of the multiple argument while-schema, we have implemented an ef-
ficient and idiomatic C function. The multiple argument while-schema requires
pre-processing of the SML function to group all arguments into a tuple, and it re-
quires post-processing of the C function to separate the elements of the tuple into
single assignment-statements. The pre-processing (tupling) is, to some extent, the
inverse of the post-processing (untupling). In this example, the post-processing
was particularly easy. This is not always the case, as the next example will show.
Exercise 3.5 Show the table of correspondence between the euclid function and
the while-schema.
The multiple argument while-schema yields:
int euclid( int m, int n ) {
while( n > 0 ) {
/*!*/ (m,n) = (n,m % n) ;
}
return m ;
}
The line marked /*!*/ must again be transformed into a sequence of assign-
ments. It would be tempting to simply separate the two components of the tuples
on the left and right hand sides of the multiple assignment and assign them one
by one as follows:
m = n ;
n = m % n ; /* WRONG */
This is wrong, because first changing the value associated with m to that of n
would cause n to become 0 always. To see this, consider the following execution
trace. We are assuming that m and n are associated with some arbitrary values ✟
and :
✂
m: ✟ n: ✂ (Step 1)
m = n ;
n = m % n ; /* WRONG */
m = n ; m: ✂ n: ✂ (Step 2)
n = m % n ; /* WRONG */
Revision: 6.41
68 Chapter 3. Loops
m = n ;
n = m % n ; /* WRONG */ m: ✂ n: ✪ (Step 3)
First assigning n and then m would not improve the situation. The problem is that
the two assignment statements are mutually dependent. The only correct solution
is to introduce a temporary variable, say old_n, as shown below. The temporary
variable serves to remember the current value in one of the cells involved, whilst
the value in that cell is being changed.
int euclid( int m, int n ) {
while( n > 0 ) {
const int old_n = n ;
n = m % old_n ;
m = old_n ;
}
return m ;
}
The declaration of old_n above is a local constant declaration rather than a local
variable declaration. This is sensible because old_n is not changed in the block in
which it is declared. We are making use of the fact that the local variables and con-
stants of a block exist only whilst the statements of the block are being executed.
So each time round the while-loop a new version of old_n is created, initialised,
used twice and then discarded. The cell is never updated, so it is a constant.
The multi-argument while-schema gives us an efficient and correct C im-
plementation of Euclid’s greatest common divisor algorithm. As we shall see,
since most functions have no mutual dependencies in the multiple assignment-
statement, the post-processing is thus often quite straightforward.
Exercise 3.6 Trace the execution of euclid(9,6) using the iterative version of
euclid above.
✞
☎
✁ (3.3)
✟✆✠
✂
We have seen the recursive SML function to compute the factorial before:
(* fac : int -> int *)
fun fac n = if n > 1
then n * fac (n-1)
else 1 ;
Revision: 6.41
3.3. While loops 69
The test n > 1 yields true because n is associated with the value 3, so that at
Step 2 the then-branch of the conditional is chosen.
int fac( int n ) {
if( n > 1 ) {
return n * fac (n-1) ; (Step 2)
} else {
return 1 ;
}
}
This causes a call to be made to fac(2) at Step 3. The number 3 will have to be
remembered for later use when fac(2) has delivered a value that can be used for
the multiplication.
int fac( int n ) { n: 2
(Step 3)
if( n > 1 ) { n :3
return n * fac (n-1) ;
} else {
return 1 ;
}
}
Revision: 6.41
70 Chapter 3. Loops
This causes another call to be made, this time to fac(1) at Step 5. The number 2
will have to be remembered for use later, just like the number 3.
n: 1
int fac( int n ) { n :2 (Step 5)
if( n > 1 ) { n :3
return n * fac (n-1) ;
} else {
return 1 ;
}
}
At Step 8 the second call returns 2, which is calculated from the return value of
Step 7 and the saved value of n.
Revision: 6.41
3.3. While loops 71
Summarising, because the function fac is not tail recursive, we have the following
efficiency problem:
✎✷✍✏✎ ✍ ✍ ✍
Firstly, the fac function generates the values ✏✕✏✒✏ on entry to the
✂
recursion.
✎✷✍✏✎ ✍ ✍ ✍ ✎
All values ✏✒✏✒✏ have to be remembered, which requires cells in
✂
the store.
✁
✎ ✎ ✆ ✁ ✁✶✎ ✟ ✆ ✁ ✆ ✁ ✆ ✟ ✟ ✟
✏✒✏✕✏ ✏✒✏✒✏
✂
✁ ✝
✎
Here, the parentheses signify that, before can be multiplied to some number, the
latter must be computed first. The efficiency of the implementation can be im-
proved if previous numbers do not have to be remembered. This is possible as
multiplication is an associative operator. Therefore we have:
✂☎ ✡ ✂☎
✄
✎ ✁ ✁ ✁✶✎ ✆ ✁✌✎ ✟ ✟ ✆ ✟ ✆ ✟ ✆
✏✒✏✒✏ ✏✒✏✒✏
✂
✁ ✝
✎ ✍✏✎ ✎
Now there is no need to remember the numbers ✏✒✏✒✏ . The number can be
✎ ✎
multiplied immediately by . This in turn can be multiplied directly by , ✝
and so on. The successive argument values for the operator can be generated and ✁
used immediately. The only value that needs to be remembered is the accumulated
result. This corresponds to multiplying on entry to the recursion.
Revision: 6.41
72 Chapter 3. Loops
Exercise 3.8 Prove by induction over n that for all natural numbers b the fol-
lowing equality holds: b * n ✁fac_accu n b. Then conclude that
n✁ fac_accu n 1.
Exercise 3.9 Show the correspondence between the SML program and the while-
schema.
The function fac_accu is is tail recursive. It can therefore be implemented in C
using a while-loop:
int fac_accu( int n, int b ) {
while( n > 1 ) {
/*!*/ (n,b) = (n-1,n*b) ;
}
return b ;
}
The multiple assignment can be replaced by two separate assignment-statements
as there is no mutual dependency between the two resulting assignments. They
must be properly ordered, so that n is used before it is changed:
int fac_accu( int n, int b ) {
while( n > 1 ) {
b = n*b ;
n = n-1 ;
}
return b ;
}
This shows that tail recursion, and therefore loops, can be introduced by using an
accumulating argument. It is not always easy to do this and it requires creativity
and ad hoc reasoning. In the case of the factorial problem, we had to make use of
the fact that multiplication is an associative operation. In other cases shown later,
similar reasoning is necessary.
The resulting version fac_accu of the factorial is slightly odd. It has two ar-
guments: the first argument is the number from which the factorial is to be com-
puted, the second number should always be one. A more useful version of facto-
rial would have only one argument:
int fac( int n ) {
return fac_accu( n, 1 ) ;
}
Revision: 6.41
3.3. While loops 73
This is neither the most efficient nor the most readable version of fac, the function
fac_accu is a specialised function and is probably never called from any other
function. In that case we can inline functions: we can amalgamate the code of two
functions to form one function that performs the whole task. This is more efficient
and it improves the readability of the code. Inlining is a seemingly trivial opera-
tion: we replace a function call in the caller with the body of the called function,
that is, the callee. However, there are some potential pitfalls:
1. The C language does not provide good support for literal substitution. Spe-
cial care has to be taken with a return statement in the callee. After inlining
in the caller, the return statement will now exit the caller instead of the callee.
3. Function arguments are passed by value. This means that the callee can
change its argument without the caller noticing. After inlining, the changes
become visible to the caller. This might require the introduction of local vari-
ables
In the case of factorial, the inlining operation results in the following code:
int fac( int n ) {
int b = 1 ;
while( n > 1 ) {
b = n*b ;
n = n-1 ;
}
return b ;
}
Because the argument b of fac_accu was changed, we needed to introduce a lo-
cal variable. The local variable is initialised to 1.
Other places where local variables are introduced are as a replacement for the
let clauses in SML. Any expression that has been given a name with a let is
in general programmed with a local variable in C. This will be shown in the next
section where local variables are used extensively. Over the course of this chapter,
more functions will be inlined to show how to compose larger C functions.
✁
Many of the while loops will have assignments with the following patttern:
✟ = ✟ ; ✂
Here, stands for one of the possible C operators. Examples include both assign-
ments in the while loop of fac above:
b = n*b ;
n = n-1 ;
Revision: 6.41
74 Chapter 3. Loops
Because these patterns are so common, C has special assignment operators to deal
with these patterns. Any assignment of the pattern above can be written using
one of the assignment operators *=, /=, %=, +=, -=, <<=, >>=, &=, |=, and ˆ=.
The assignments below have equivalent semantics. Here is a variable and an
✟ ✂
This is the case for any of the operators *, /, %, +, -, <<, >>, &, |, and ˆ. Thus
i=i+2 and i+=2 (read: add 2 to i) are equivalent, and so are j=j/2 and j/=2
(read: divide j by 2).
Using these assignment operators has two advantages. Firstly, they clarify the
meaning of the code, as j/=2 means divide j by two as opposed to find the cur-
rent value of j, divide it by two, and store it in j (which is by coincidence the same
variable). The second advantage is a software engineering advantage; when more
complex variables are introduced, these operators take care that the variable is
specified only once. An example of this is shown in Chapter 5.
Each assignment operation is actually an expressions in itself. For exam-
ple, j/=2 divides j by two and results in this value. Therefore the expression
i += j /= 2 means divide j by two and add the result of that operation to i.
Assignments placed in the middle of expressions can cause errors as the order
of assignments is undefined. It is recommended to use the assignment only as a
statement.
As a final abbreviation, there are special operators for the patterns += 1 and
-= 1, denoted ++ and --, also known as the increment and decrement op-
erators. They exist in two different forms: the pre-increment ++ and the post-
increment ++. The first version is identical to +=1 or = +1: add one to and use
the resulting value as the value of the expression ++ . The post-increment means
add one to , but use the old, non-incremented value as the value of the expression.
✎ ✎
Revision: 6.41
3.3. While loops 75
Local definitions.
(*SML general tail recursion schema*)
(* : (✁✄✂ * ... ✁✆☎ ) -> ✁✆✝ *)
fun ( ✟✡✂ , ... ✟☛☎ )
= let
val ✂ = ✂ (* ✂ : ✁
✆
✂ ✂
✝
*)
...
val ✂✁ = ✁ (* ✄✁ : ✁ ✆☎
✆
✂ ✂ *)
in
if ☞ ✂
then ✝ ✂
else
...
if ☞
✁
then ✝
✁
else ✝ ✂
✁
end ;
Revision: 6.41
76 Chapter 3. Loops
The ✟ ✂✑✏✒✏✒✏✓✟✔☎ are the arguments of , their types are ✁✕✂✑✏✒✏✕✏✖✁✞☎ respectively. The local
variables of are ✂✑✏✒✏✒✏ ✂✁ ; their values are the expressions ✂✑✏✒✏✒✏ ✁ , and their types
✆ ✆
✂ ✂
are ✁ ✏✒✏✒✏✔✁ ✆☎ respectively. The expressions ☞ ✂ ✏✒✏✒✏ ☞ are predicates over the argu-
✁
ments and the local variables. The ✝ ✂✑✏✒✏✒✏ ✝ ✂ are expressions that may take one of
✝
two forms. This form decides whether the present branch in the conditional is a
termination or a continuation point:
continued from here with new values for the arguments as computed by the
expression ✌ . In this case, ✌ must be of type (✁ ✂ * . . . ✁ ☎ ).
✟ ✟
termination The form of ✝ is ✍ , which means that the recursion on may termi-
✟ ✟
nate here. The function result is delivered by the expression ✍ . In this case, ✟
The ✌ and ✍ are all expressions ranging over the arguments and local variables.
✟ ✟
The only calls permitted to are those explicitly listed in the continuation case
above.
The general while-schema that corresponds to the general tail recursion
schema is:
/*C general while-schema*/
✁✞✝ ( ✁✄✂ ✟✡✂ , ... ✁✆☎ ✟✔☎ ) {
✁ ✝
✂ ; ✂
...
✁ ☎ ✂✁ ; ✂
while( true ) {
✂ = ✂ ;
✆
...
✂✁ = ✁ ;
✆
if( ☞ ✂ ) ✝ ✂ ;
else if( ☞ ) ✝ ;
...
else if( ☞ ) ✝ ;
✁ ✁
else ✝ ✂ ;
✁
}
}
The loop is now in principle an endless loop, which can be terminated only by
one of the conditional statements if( ☞ ) ✝ ;. Depending on the form of the ✟ ✟
✝ in the general tail recursion schema, the corresponding ✝ in the general while-
✟ ✟
ment { ( ✟ ✂ , . . . ✟✔☎ )=✌ ;}. The loop will be reexecuted with new values ✟
for ✟✡✂✡✏✕✏✒✏✖✟✔☎ .
;}. This
causes the loop (and the function ) to terminate.
Revision: 6.41
3.3. While loops 77
schema: Functional C
✎
: bisection bisection
: 3 3
(✁✄✂ * (real->real* (double(*)(double),
✁✁ *✁ ):
✆
real*real) double,double)
✁✞✝ real double
( ✟ ✂ , ✟ , ✟ ):
✆
f,l,h f,l,h
☞ ✂ : absolute f_m < eps absolute(f_m) < eps
☞ : absolute (h-l) < delta absolute(h-l) < delta
☞ :
✆
(f,m,h) { (f,l,h)=(f,m,h) ;}
✌ :
✄
(f,l,m) { (f,l,h)=(f,l,m) ;}
✁ : ✝
real double
✁ : real double
✂
✂ : m m
✂ : f_m f_m
✂ :
✆
✆
(l + h) / 2 (l + h) / 2
: f m f(m)
Revision: 6.41
78 Chapter 3. Loops
may write l=m; and h=m; respectively. This gives the following C version:
double bisection( double(*f)( double ),
double l, double h ) {
double m ;
double f_m ;
while( true ) {
m = (l+h)/2.0 ;
f_m = f(m) ;
if( absolute( f_m ) < eps ) {
return m ;
} else if( absolute( h-l ) < delta ) {
return m ;
} else if( f_m < 0.0 ) {
l = m ;
} else {
h = m ;
}
}
}
What we have done so far is to systematically transform an SML function with
multiple exits, multiple continuation points, local declarations, and multiple argu-
ments into a C function. Let us now reflect on the C function that we have derived.
Worth noting is the use of the condition true in the while-statement. This creates
in principle an ‘endless’ loop. It signals that there is no simple condition for decid-
ing when the loop should be terminated.
Exercise 3.10 Complete the skeleton below by giving the contents of the body of the
while-loop. Comment on the efficiency and the elegance of the implementa-
tion.
double bisection( double(*f)( double ),
double l, double h ) {
double m = (l+h)/2.0 ;
double f_m = f(m) ;
while( ! ( absolute( f_m ) < eps ||
absolute( h-l ) < delta) ) {
/*C body of the while-loop*/
}
return m ;
}
Revision: 6.41
3.4. For loops 79
Two methods have been shown for breaking out of a while-loop: the condition
of the while-loop can become false, or a return statement can be executed (which
terminates the function and therefore the while-loop). C offers a third method, the
break-statement. Whenever a break is executed, a while-loop is terminated, and
execution resumes with the statement immediately following the while-loop.
We do not need a break statement in any of these examples because the func-
tions contain only a single while-loop. However, when functions are inlined to
make larger entities, it might be necessary to break out of a loop in a callee to avoid
terminating the caller. Inserting a break statement will do the trick in that case. A
break will only jump out of the closest while loop, breaking about multiple while
loops is not possible, normally a return is used in that case.
Exercise 3.11 Inline the function bisection in the following main program:
int main( void ) {
double x = bisection( parabola, 0.0, 2.0 ) ;
printf("The answer is %f\n", x ) ;
return 0 ;
}
What is the problem? What is the solution?
✄
✂ ☎ ✡ ✂ ☎
✞
☎
✟✆✠
✂
The computation of starts by generating the value and steps through the val-
✁
ues , , and so on, until it reaches . The ✡ operator has built-in knowledge about
✂
the following:
✁
evaluate and accumulate the values of the expression (here just ), with the
index bound to a sequence of values.
✁
Revision: 6.41
80 Chapter 3. Loops
The ✡ operator has a direct functional equivalent with a list, where the list is
✎
Revision: 6.41
3.4. For loops 81
((( 1 * 1) * 2) * 3)
(( 1 * 2) * 3)
( 2 * 3)
6
= foldl ✌
☞ ✎
( -- ) ;
Here the ✟ ✂ ✏✒✏✒✏✓✟ are variables of types ✁ ✂ ✏✒✏✒✏✖✁ . The ✌ is an expression that rep-
✁ ✁
resents a function of type ✁ ✝ -> int -> ✁ ✝ , the is of type ✁ ✝ , and and are
☞ ✎
expressions of type int. The variables ✟✘✂✗✏✒✏✒✏✄✟ may occur in the expressions ✌ ,
☞ ✎
✁
, , and . The function name should not occur on the right hand side of the
✁
equals sign.
Revision: 6.41
82 Chapter 3. Loops
☞ ✎
✁
int ;
✁✆✝
✁
= ; ☞ ✎
for( = ; <= ; ++ ) {
= ✌ ( , ) ;
}
return ;
}
The for-statement has the following elements:
✁
The index which acts as the loop counter. It must be declared explicitly.
☞
A loop body statement =✌ ( , );, which, using the index , does the
actual work. This consists of accumulating the results of the loop in the accu-
mulator .
The for-statement in the for-schema is preceded by two declarations. The first de-
clares the index . It is not necessary to give the index an initial value as the for-
✂
statement will do that. The second declaration ✁✄✝ = ; declares the accumulator
✁
for the loop. The accumulator must be initialised properly. The accumulator is up-
dated in the loop, using the values generated by the index and the current value
of the accumulator.
When instantiating the increasing for-schema, it is best to choose names for
and that do not already occur in the function that is being created. In particular
one should avoid the names of the arguments of the function.
☞ ✱ ✎
int ;
✁ ✝
✁
= ; ☞ ✎
for( = ; >= ; -- ) {
= ✌ ( , ) ;
}
return ;
}
Revision: 6.41
3.4. For loops 83
The differences between the increasing and the decreasing for loops are in their
test for termination and the iteration expression:
✁
In the decreasing case, the test for termination tests on greater or equal,
✎
>= .
✁
schema: Functional C
: fac fac
: 1 1
✟✡✂ : n n
✁✄✂ : int int
✁✞✝ : int int
✌ : mul *
:
✁
☞
1 1
✎
: 1 1
: n n
This yields the following C implementation below, where mul( accu, i ) has
been simplified to accu * i:
int fac( int n ) {
int i ;
int accu = 1 ;
for( i = 1; i <= n; i++ ) {
accu = accu * i ;
}
return accu ;
}
To investigate the behaviour of the for-statement, the execution of fac(3) will be
traced. At Step 1 the argument n is allocated, and the variable is associated with
the value 3.
Revision: 6.41
84 Chapter 3. Loops
At Steps 2 and 3 the local variables i and accu are allocated. The value associ-
ated with i variable is undefined. This is indicated by the symbol . The value
associated with accu is 1.
int fac( int n ) {
int i ; n: 3
int accu = 1 ; i: (Step 3)
for( i = 1; i <= n; i++ ) { accu : 1
accu = accu * i ;
}
return accu ;
}
At Step 4 two actions take place. Firstly, the value 1 will be associated with the
local variable i. This is the initialisation step of the for-statement. Secondly, a test
will be made to see of the current values of i and n satisfy the condition i <= n.
In the present case, the condition yields true.
int fac( int n ) {
int i ;
int accu = 1 ; n: 3
for( i = 1; i <= n; i++ ) { i: 1 (Step 4)
accu = accu * i ; accu : 1
}
return accu ;
}
this cannot be seen in the rendering of the store. At step 6 the control
variable is incremented to 2.
Revision: 6.41
3.4. For loops 85
At Step 8 the body of the for-statement is re-executed, so that the value associated
with accu is updated. At Step 9 the value associated with the control variable is
incremented to 3 and at Step 10 control is transferred back to the beginning of the
for-statement. The condition yields true again.
int fac( int n ) {
int i ;
int accu = 1 ; n: 3
for( i = 1; i <= n; i++ ) { i: 3 (Step 10)
accu = accu * i ; accu : 2
}
return accu ;
}
Steps 11, 12 and 13 make another round though the for-loop, producing the state
below. Control has been transferred back to the beginning of the for-statement for
the last time. This time the condition yields false. The for-statement has termi-
nated.
Revision: 6.41
86 Chapter 3. Loops
This concludes the study of the behaviour of the for-statement using the optimised
factorial function. As expected, the amount of space that is used during the exe-
cution is constant, which is the reason why this version is more efficient than the
recursive version.
The increasing and decreasing for-schemas form another useful technique that
will help to build efficient and idiomatic C implementations for commonly occur-
ring patterns of computation.
Revision: 6.41
3.4. For loops 87
foldl
✁
✁
✟ ✂
✁
✟
✁
✟
✆
✁✄✂ ✟ ✟ ✟
✟✡✂ ✟ ✟
✁ ✁ ✁ ✟ ✟ ✆ ✟
by contrast
foldr
✁
✁
✂ ✂
✁
✂
✁
✂
✆
✁✄✂ ✟ ✟ ✟
✂
✁ ✁ ✁ ✆ ✟ ✟ ✟
✂ ✂ ✂
Folding from the right by foldr gives rise to a second set of two for-schemas:
(*SML foldr schema*)
(* : ✁✄✂ -> ... -> ✁ -> ✁✞✝ *) ✁
= foldr ✌
☞ ✎
( -- ) ;
As before, the ✟ ✂✑✏✒✏✒✏✓✟ are variables of types ✁ ✂✑✏✒✏✒✏✓✁ . The ✌ is an expression that
✁ ✁
represents a function of type int -> ✁✖✝ -> ✁✞✝ , the is of type ✁✓✝ , and the and
☞
are expressions of type int. The variables ✟ ✂✗✏✕✏✒✏✄✟ may occur in the expressions
✎
✌ , , , and . The function name should not occur on the right hand side of the
☞ ✎
equals sign. ☞ ✎
Again, there are two cases depending on the ordering of and . Consider the
☞ ✱ ✎
case that . The C implementation of the foldr-schema now starts from the
right and increases the index :
/*C increasing right folding for schema*/
✁ ✝ ( ✁ ✂ ✟ ✂ , ... ✁ ✟ ) { ✁ ✁
✁
int ;
✁✆✝
✁
= ; ✎ ☞
for( = ; <= ; ++ ) {
= ✌ ( , ) ;
}
return ;
}
The differences between the increasing left and right folding versions of the for-
statement are:
☞ ✎
In the case that , changes have to be made similar to those discussed in the
previous section for the left folding for-statement. In total, there are four differ-
ent cases of folding, each with its own C for-statement. The key elements are the
following:
Revision: 6.41
88 Chapter 3. Loops
☞ ✎ ☞✲✱ ✎
✎ ☞ ✎ ☞ ✎
☞ ✎ (; ☞ ✎ ;( ☞
✡
from
✁ ✁
foldr -- ) foldr -- )
☞ ☞ ✎ ☞ ✎
✌ ✌
to
✎
from foldl
for(
✌
=
✁
☞ (
;
-- ) ✎
foldl
<= ; ++) for( =
✌
✁
☞ (
;
-- )
>= ; --)
✎
The differences between the four versions of the for-schema may seem small, but
they are significant. Programming errors caused by the use of an incorrect for-
statement are usually difficult to find. Errors, such as the inadvertent swapping
of the upper and lower bounds, cannot be detected by the compiler, since both
bounds will have the same type. Painstaking debugging is the only way to track
down such errors. The four schemas that we have presented provide guidance
in the selection of the appropriate for-statement and should help to avoid many
programming errors.
Exercise 3.12 In the previous chapter we introduced the function repeat to cap-
ture the common behaviour of the operators and . In this chapter
☛ ✡
Revision: 6.41
3.5. Generalizing loops and control structures 89
Revision: 6.41
90 Chapter 3. Loops
The second problem is solved by using one of the standard equations that relate
foldl and map. It has a more general form than we need right now, but it is often
✍
a good idea to try to state and prove as general a result as possible. When given a
function such that:
fun ✍ ✟ ✁
✌ ✁
( ✟)
Then the following equation is true for all finite lists ✟ and all , ✌ and :
✁
✌ ✠✟ ✍ ✟
✄
(3.4)
✁ ✁
✟
✄ ✄
To make use of the equation (3.4), we need to identify the functions , and . Let
us put the essential ingredients in a table of correspondence so that it is possible to
see the relationships:
✌
square
✍
add
✍ ✟ =✌ ✠✟
add_square
✁ ✁
The net result of using the correspondences of the table above is:
(* add_square : int -> int -> int *)
fun add_square b x = add b (square x) ;
Revision: 6.41
3.5. Generalizing loops and control structures 91
int accu = 0 ;
for( i = 1; i <= n; i++ ) {
accu = add_square( accu , i ) ;
}
return accu ;
}
As a finishing touch, we can inline the body of add and square in that of
add_square and then inline the body of add_square in sum_of_squares. In
this case, the inlining is straightforward as the C functions add, square, and
add_square all contain just a return-statement. Here is the result of the inlining:
int sum_of_squares( int n ) {
int i ;
int accu = 0 ;
for( i = 1; i <= n-1; i++ ) {
accu = accu + (i * i) ;
}
return accu ;
}
This shows that we have achieved the two goals. Firstly, we have a way of dealing
with an expression that is not just a left folding over an arithmetic sequence, so
we have generalised the for-schema. Secondly, we have created a slightly larger C
function than we have been able to do so far.
✎
✂ ✝ ✂ ✂ ✝ ✂ ✂ ✝ ✂
write a function that determines whether its argument is a perfect number. Here
is the specification that a perfect number has to satisfy:
✂☎ ✡ ✂
perfect
perfect
✁✶✎✸✟ ✎ ✗ ☛ ✡
✎✞✢ ✎ ✒✦✥★✧ ✫✪ ✯ (3.5)
Let us develop the solution by top-down program design: Assume that we already
have a function sum_of_factors to compute the sum of those factors of a num-
ber that are strictly less than the number itself. Then, the first part of the solution
is:
(* perfect : int -> bool *)
fun perfect n = n = sum_of_factors n ;
The second problem is to compute the sum of the factors. This problem has three
parts, each corresponding to the three important elements of the problem specifi-
cation (3.5):
1. Generate all potential factors of n which are strictly less than n. This requires
an arithmetic sequence (1 -- n-1).
Revision: 6.41
92 Chapter 3. Loops
2. Remove all numbers i from the list of potential factors that do not satisfy
the test n mod i = 0, as such i are relatively prime with respect to n. The
standard filter function removes each element for which rel_prime re-
turns true.
3. All remaining elements of the list are now proper factors, which can be
summed using the standard function sum.
The results of the problem analysis give rise to the following program:
(* rel_prime : int -> int -> bool *)
fun rel_prime n i = n mod i = 0 ;
(* sum_of_factors : int -> int *)
fun sum_of_factors n
= sum (filter (rel_prime n) (1 -- n-1)) ;
For ease of reference, here is the definition of the standard function filter:
(* filter : (a->bool) -> a list -> a list *)
fun filter p [] = []
| filter p (x::xs) = if p x
then x :: filter p xs
else filter p xs ;
As in the previous section, to derive a C implementation of sum_of_factors we
need to be able to match its SML definition to the foldl-schema. Substituting the
definition of sum exposes the foldl:
(* sum_of_factors : int -> int *)
fun sum_of_factors n
= foldl add 0 (filter (rel_prime n) (1 -- n-1)) ;
To remove the filter we use an equation relating foldl and filter. This
equation is similar tho that used to remove the map earlier. When given a func-
tion such that:
✍
✁ ✁ ✁
✟ ✄ ☞ ✌
(3.6)
✁ ✁
= if (rel_prime n) x
then add b x
else b
Revision: 6.41
3.5. Generalizing loops and control structures 93
should ask ourselves the question: where does the n come from? If we were to try
the following definition, the SML compiler would complain about the variable n
being undefined:
(* add_rel_prime : int -> int -> int *)
fun add_rel_prime b x
= if (rel_prime n) x
then add b x
else b ;
The solution to this problem is to make n an extra argument of add_rel_prime,
as we have shown in the table of correspondence above.
We are now able to write a new version of sum_of_factors consisting of an
arithmetic sequence and a call to foldl:
(* add_rel_prime : int -> int -> int -> int *)
fun add_rel_prime n b x
= if (rel_prime n) x
then add b x
else b ;
Revision: 6.41
94 Chapter 3. Loops
Revision: 6.41
3.5. Generalizing loops and control structures 95
can be combined.
The substitution of C functions as shown above is not always valid. This is
only safe where pure functions are considered. Functions that rely on side effects,
which will be shown in Chapters 4 and 5, do not always allow for safe substitu-
tion.
0 1 2 3 4 5
0 1 0 0 0 0 0
1 1 1 1 1 1 1
✎
2 1 2 4 8 16 32
3 1 3 9 27 81 243
4 1 4 16 64 256 1024
We want to compute the sum of all these powers, 1799 in the case of the above
table. In general, the sum is:
✂☎ ✡ ✂☎
sum of sum of powers
☎
✁✌✎✑✟ ✁ ✟
✁
Revision: 6.41
96 Chapter 3. Loops
3.6 Summary
The following C constructs were introduced:
Revision: 6.41
3.6. Summary 97
While-loops The while loop executes statements repeatedly, until the while con-
dition becomes false. The general form of the while loop is:
while( ) {
☞
}
The statements are executed for as long as the condition ☞ does not evalu-
ate to 0.
For-loops The for loop executes statements repeatedly, until the for condition be-
comes false. The general form of the for loop is:
✂ ☎
for( ; ;☞ ) {
}
✂
Before the loop starts, is executed, to initialise. While the condition eval-
☎
☞
uates to true, the statements followed by the iteration are executed. The
most frequently used forms of these loops are the incrementing and decre-
menting for loop:
✎ ✎
} }
Here stands for any integer variable, and is the number of times that the
loop is to be executed.
Breaking out of a loop The statement break; will cause the execution of a loop
to be terminated immediately. Execution resumes after the closing curly
bracket of the loop body.
Revision: 6.41
98 Chapter 3. Loops
From a programming principles point of view, the most important issues that we
have addressed are the following:
✁
Side effects should be visible, and they should be localised. A function may
internally use side effects to increase efficiency; however, externally it should
not be distinguishable from functions that do not use side effects.
✁
Functions should have clear and concise interfaces. That is, if the same func-
tionality can be obtained using fewer arguments, then this is to be preferred.
✁
The C programmer has to make sure that values are computed and assigned
to variables before they are used. It is good programming practice to always
initialise local variables.
✁
Tail recursive functions are preferred over non-tail recursive functions since
the former can efficiently be implemented as loops. Non-tail recursive func-
tions can often be transformed into tail recursive functions by expedient use
of ad-hoc transformations, such as the accumulating argument technique.
width and height to print out a width height chess board using ASCII
characters. If width and height the output should look as fol-
✁
lows:
---------
| |X| |X|
---------
|X| |X| |
---------
Revision: 6.41
3.7. Further exercises 99
Exercise 3.19 Using a loop, write a function that determines the root of a continu-
ous function using the Newton-Raphson method. Use the answer of Exer-
cise 2.26 as the starting point.
Revision: 6.41
100 Chapter 3. Loops
Revision: 6.41
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 4
4.1 Structs
The primitive data structures discussed in the previous chapter make it possible
to pass single numbers around. Often, one is not interested in single numbers
but in a collection of numbers. An example of a collection
✁ ✍
of✟ numbers is a point
in a plane, say . The point is represented by a tuple
☞ , where is the X- ☞ ☞ ☞
✁ ✍
to ✟ perform
on a point is to rotate it around the origin. To rotate a point around ☞ ☞ ☞
the origin by 90 degrees in the clockwise direction, we compute a new point, say
, with coordinates ☞ and : ☞
✍ ✁✄✂ ✝ ✆✞✂ ✝ ✟
☞
✁ ✍ ✟ ✁ ✍ ✟
☞ ☞
101
102 Chapter 4. Structs and Unions
However, a long name like that is not practical, so we will use the rather conven-
tional rotate.
(* rotate : point -> point *)
fun rotate (p_x,p_y) = (p_y,0.0-p_x) ;
The following equalities are all true:
rotate ( 1.0, 0.0) = ( 0.0,˜1.0) ;
rotate (˜0.1,˜0.3) = (˜0.3, 0.1) ;
rotate ( 0.7,˜0.7) = (˜0.7,˜0.7) ;
These three rotations are shown graphically in the figure below:
+ +
+
+ +
+
C does not offer plain tuples, but it provides a more general mechanism: the
struct. The general form of a struct declaration is:
struct { ☞
✁ ✁ ; ☞
✁
✁ ;✂ ✂
...
}
☞
also known as members. A typedef can be used to associate a type name with the
structured type. The declaration of a struct that contains the two coordinates of a
point reads:
typedef struct {
double x ;
double y ;
} point ;
This declares a struct with two members x and y, and gives it the name point.
The function for rotating a point should be written as follows:
point rotate( point p ) {
point r ;
r.x = p.y ;
r.y = -p.x ;
return r ;
}
The C function rotate has a single argument of type point. The return value is
also of type point. The body of rotate allocates a local variable r of type point
and makes two separate assignments to the members of r. Members are referred
to using a dot-notation, so p.y refers to the value of the member with name y
in the point-structure p. Therefore, the assignment r.x = p.y means: take the
Revision: 6.37
4.1. Structs 103
value of the y member of p and assign this value to the x member of r. The y
member of r is not altered. The second assignment fills the y member of r. Fi-
nally, the value of r is returned. This is different from the SML version of rotate,
which creates its result tuple in a single operation. Unfortunately, C does not have
a general syntax to create a struct, in contrast with most functional languages
that do have syntax to create tuples.
To print a point, a new function has to be defined, because printf has no
knowledge how to print structured data. This new function print_point is
shown below:
void print_point( point p ) {
printf( "(%f,%f)\n", p.x, p.y ) ;
}
The return type of this function is void. This means it will return nothing. This
function would not have a meaning in a pure functional language, as it cannot
do anything. In an imperative language, such functions do have a meaning, as
they can print output as a side effect. Another use of functions with a void type
is shown in Section 4.4, where the different argument passing methods of C are
discussed. Some imperative languages have a special notation for a function that
does not return a value; such a function is called a procedure in Pascal or a subrou-
tine in Fortran.
The body of print_point consists of a call to printf with three arguments:
the format string and the values of the x and y coordinates of the point. The for-
mat string and the arguments are handled exactly as discussed in Chapter 2. The
parentheses, comma, and the newline character are printed literally, while the %f
parts are replaced with a numerical representation of the second and third argu-
ments.
The function main shown below declares a local variable r of type point.
✁ ✍ ✟
this point and prints the result, producing the output ✪ . This is followed by
two similar calls to assign new values to the point r, rotate the point, and print the
result.
int main( void ) {
point r ;
r.x = 1 ; r.y = 0 ;
print_point( rotate( r ) ) ;
r.x = -0.1 ; r.y = -0.3 ;
print_point( rotate( r ) ) ;
r.x = 0.7 ; r.y = -0.7 ;
print_point( rotate( r ) ) ;
return 0 ;
}
The function main shows again that C does not provide syntax to create a struct
directly. However, there is one exception to this rule. In the declaration of a con-
stant or in the initialisation of a variable, one can create a struct by listing the
values of the components of the struct if all these values are constants. Using
Revision: 6.37
104 Chapter 4. Structs and Unions
area
✁ ✁ ✍ ✟✏✍ ✁✁ ✍✂ ✟ ✟ ✢ ✁✄ ✟ ✆ ✁✄ ✟ ✢
area (4.1)
Revision: 6.37
4.3. Unions in structs: algebraic data types 105
In C, the type of the rectangle is defined as follows, where we have repeated the
definition of point for ease of reference:
typedef struct {
double x ; /* X-coordinate of point */
double y ; /* Y-coordinate of point */
} point ;
typedef struct {
point ll ; /* Lower Left hand corner */
point ur ; /* Upper Right hand corner */
} rectangle ;
The function to calculate the area then becomes:
double area( rectangle r ) {
return absolute( ( r.ur.x - r.ll.x ) *
( r.ur.y - r.ll.y ) ) ;
}
The argument r refers to the whole rectangle, r.ll refers to the lower left hand
corner, and r.ll.x refers to the X-coordinate of the lower left hand corner. Simi-
larly, r.ur.x refers to the X-coordinate of the upper right hand corner.
In this example both point and rectangle were defined as a new type, using
typedef. It is equally valid only to define the type rectangle, and to embed the
structure storing a point in it:
typedef struct {
struct {
double x ; /* X-coordinate of point */
double y ; /* Y-coordinate of point */
} ll, ur ; /* Lower left & Upper right */
} rectangle ;
This does not define a type for point, hence points cannot be passed on their own
(because they have no type with which to identify them). In general, one will em-
bed a structure (as opposed to defining a separate type), when the structure on its
own does not have a sensible meaning.
when a particular data type may have different interpretations and/or alternative
forms. For example, the following type can be defined in SML to encode a distance
in either Ångströms, miles, kilometres, or light years.
datatype distance = Angstrom of real |
Revision: 6.37
106 Chapter 4. Structs and Unions
Mile of real |
Kilometre of real |
Lightyear of real ;
Using the data type distance we can write a conversion function that takes any
one of the four different possibilities and calculates the distance in millimetres.
Pattern matching is used to select the appropriate definition of mm:
(* mm : distance -> real *)
fun mm (Angstrom x) = x * 1E˜7
| mm (Kilometre x) = x * 1E6
| mm (Mile x) = x * 1.609344E6
| mm (Lightyear x) = x * 9.4607E21 ;
Adding distances that are specified in different units is now possible, for example:
mm (Mile 1.0) + mm (Kilometre 4.0) = 5609344.0 ;
C does not have a direct equivalent of the algebraic data type. Such a type must
be composed using the three data structuring tools enum, struct, and union.
The enum (Section 2.2.6) will enumerate the variants, the struct (Section 4.1) will
hold various parts of the data, the union, defined below, allows to store variants.
✁ ✁ ; ☞
✁
✁ ;✂ ✂
...
}
☞
The difference between a struct and a union is that the latter stores just one of
its members at any one time, whereas the former stores all its members at the same
time. A struct is sometimes called a product of types, while a union is a sum
of types. In SML, we have the same distinction: the alternatives in an algebraic
data type declaration (as separated by vertical bars) represent the sum type, and
the types within each alternative represent a product type.
The storage model can be used to visualise the differences between a struct
and a union. Assume that we have a structure pair which is defined as follows:
typedef struct {
int x ;
int y ;
} pair ;
Below are a structure (structx) and a union (unionx), with their respective stor-
age requirements:
Revision: 6.37
4.3. Unions in structs: algebraic data types 107
Exercise 4.1 The example shown above assumes that the member d of the type
double occupies one storage cell. Often, a double occupies two storage
cells. Draw the storage requirements of structx and unionx under the
assumption that the floating point number d is stored in two storage cells.
The union of C does not provide type security. Suppose that the type uniontype
as defined earlier is used as follows:
int abuse_union( double x ) {
uniontype y ;
y.d = x ;
return y.i ;
}
When abuse_union( 3.14 ) is executed, it will first create a variable y of type
uniontype and write the value 3.14 in the d member of the union. Then the value
of the i member of the union is returned. This is legal C, but logically it is an
improper operation. Probably, the function abuse_union will return part of the
bit pattern of the binary representation of the number 3.14, but the precise value
will depend on the peculiarities of the implementation.
To use a union properly, the programmer must keep track of the types that
are stored in the unions. Only the type that has been stored in a union should
be retrieved. In general, the programmer has to maintain a tag to specify what is
stored in a union. Because this tag has to be stored together with the union, both
are conveniently embedded in a struct. This recipe results in the following C
Revision: 6.37
108 Chapter 4. Structs and Unions
typedef struct {
distancetype tag ;
union {
double angstroms ;
double miles ;
double kilometres ;
double lightyears ;
} contents ;
} distance ;
The possible distance types are enumerated in the type distancetype. The
structure that holds a distance, has two members. The first member is the tag
that specifies the type of distance being stored. The second member is the union
that holds the values for each of the possible distances.
Exercise 4.2 There are two common notations for a point in a 2-dimensional space:
Cartesian
✁ ✍ ✟ coordinates and polar coordinates.
✁ ✍✁✰✟ Cartesian coordinates
✁ ✍ ✟ give
✁ ✍✂✰✟a
pair ✟ , the polar coordinates a pair
✂ . The meaning of ✟ ✂and
are:
✂ +
Define a data type coordinate (both in SML and C) that can hold a point
in space in either of these coordinate systems.
Exercise 4.3 Give a C function to convert a polar coordinate into a Cartesian coor-
dinate and give another C function to do the opposite. Are your functions
each other’s inverse? If not, can you find a coordinate that when converted
sufficiently many times between polar and Cartesian ‘drifts’ away from the
initial coordinate?
Revision: 6.37
4.3. Unions in structs: algebraic data types 109
case : ✄;
...
✁
default : ;
}
The first step of the execution of a switch statement is the evaluation of the ex-
pression . After that, the value of the expression is matched against all the (con-
stant) case labels . If one of the constants matches, the statements following
✄
✟
✄
✟
it, that is , are executed. Otherwise, if none of the cases match, the statements
✟
following the default label are executed. (The default part may be omitted, but
it is good programming practice to catch unforeseen cases using the default.)
The switch can be used to define the mm function, that converts any distance to
millimetres:
double mm( distance x ) {
switch( x.tag ) {
case Angstrom: return x.contents.angstroms * 1e-7 ;
case Kilometre: return x.contents.kilometres * 1e6 ;
case Mile: return x.contents.miles * 1.609344e6 ;
case Lightyear: return x.contents.lightyears*9.4607e21;
default: abort() ;
}
}
Revision: 6.37
110 Chapter 4. Structs and Unions
✪ ☛ ☞ ☛
Revision: 6.37
4.3. Unions in structs: algebraic data types 111
ALGOL-68 and Pascal found this such an important concept that they introduced
a construction that specifies both the variants and the tag, all at once. As an exam-
ple we show the C and Pascal definitions of the point data structure:
In Pascal, a floating point number is of type real. The words in a member decla-
ration in Pascal are written a different order. The enumerated types of Pascal and
C are similar:
The main difference between the two languages with respect to building struc-
tured data is that the Pascal record has a second use: it can have variants, which
correspond to the C struct/union combination. Here is the correspondence be-
tween the two languages:
The Pascal coordinate record always has a tag member, which discrimi-
nates between the Cartesian and the Polar coordinate system. If the tag is
Cartesian, then only the tag, x, and y members are logically accessible. A sim-
ilar restriction holds for Polar. The compiler can be designed to make sure that
the restriction is enforced.
In the language C++, it was also recognised that a union is often part of a
structure. Hence, it was decided that a C++-union may be anonymous. In the
Revision: 6.37
112 Chapter 4. Structs and Unions
above example, the identifier contents can be omitted so that the union may be
defined as:
typedef struct {
coordinatetype tag ;
union {
struct {
double x ;
double y ;
} cartesian ;
struct {
double r ;
double theta ;
} polar ;
} ;
} coordinate ;
The definition in C++ of a variable c of the type coordinate makes it possi-
ble to access the elements of c with c.tag, c.cartesian.x, c.cartesian.y,
c.polar.r, and c.polar.theta. This is opposed to the way the components
are accessed in C, using c.contents.cartesian.x. The use of tags is not
mandatory in C++, so unions like the one shown above are still unsafe.
Revision: 6.37
4.4. Pointers: references to values 113
i: 123
p:
The variable i is an integer with a current value 123; the variable p is a pointer
which refers to i. We say that p is a pointer referring to an integer, and the type
of p is ‘pointer to integer’. In C, this type is denoted as int *. The asterisk *
after the base type int indicates that this is a pointer to an int. The function
pointer_example contains declarations for i and p and initialises them:
void pointer_example( void ) {
int i = 123 ;
int *p = &i ;
printf("i: %d, *p: %d\n", i, *p ) ;
}
The variable p is initialised with the value &i. The prefix & is the address-operator,
it returns a reference to the variable, or, in terms of a von Neumann model, the
address of the memory cell where i is stored. The prefix-& can only be applied to
objects that have an address, for example a variable. The expression &45 is illegal,
since 45 is a constant and does not have an address.
The call to printf prints two integer values, the values of i and *p. The
prefix-* operator is the dereference-operator. It takes a pointer and returns the
value to which the pointer is pointing. It is the inverse operation of & and may
only be applied to values with a pointer type. Thus *i is illegal, as i is an inte-
ger (not a pointer), and *3.14e15 is also illegal as 3.14e15 is a floating point
number and not a pointer. The output of the function pointer_example will be:
i: 123, *p: 123
Note that it is essential for i to be a variable, and not a const; it is illegal to ap-
ply the address operator to something that is denoted const. As with any other
type, a typedef can be used to define a type synonym for pointers. The type
pointer_to_integer is defined below to be a synonym for a pointer to an int:
typedef int * pointer_to_integer ;
The general form of a pointer type is base_type *. The asterisk denotes ‘pointer
to’. The base_type may be any type, including another pointer type. This means
that we can use a type int **, which denotes a pointer to a pointer to an integer.
The asterisks bind from left to right, so this type is read as (int *)* or a pointer
to (a pointer to an integer). An extended function uses two more pointers, q and
r:
void extended_pointer_example( void ) {
int i = 123 ;
int *p = &i ;
int *q = &i ;
int **r = &p ;
printf("i: %d, *p: %d, *q: %d, **r: %d\n", i, *p, *q, **r ) ;
}
Revision: 6.37
114 Chapter 4. Structs and Unions
i: 123
p:
q:
r:
The variable q is a pointer to an int and points to the same storage cell as p. The
variable r is a pointer to a pointer to an int; it is initialised to point to p. In the
call to printf, we find the argument **r. The dereference operator * is applied
twice in succession, once to r, resulting in *r, the value to which r was pointing.
Subsequently *r is dereferenced resulting in *(*r) which results in the value of
i. The function above will print the output:
i: 123, *p: 123, *q: 123, **r: 123
It is important to fully appreciate the meaning of the & and * operator. Note that
&i has the same value as p and q and that &p has the same value as r, but that &q
has a value different from r.
Exercise 4.5 What is the meaning of the types type0 and type1 in the following
type synonyms?
typedef struct {
double *x ; int y ;
} type0 ;
typedef type0 *type1 ;
Revision: 6.37
4.4. Pointers: references to values 115
int *p = &i ;
int **r = &p ;
*p = 42 ; /* First assignment */
*r = &j ; /* Second assignment */
*p = i + 1 ; /* Third assignment */
printf("i: %d, j: %d, *p: %d, **r: %d\n", i, j, *p, **r );
}
The state of the store just after the initialisation and after each of the three subse-
quent assignments are shown in the following figure:
Although the variable i is not assigned to anywhere in the function, its value
is changed. It was 123 after the initialisation, but it has the value 42 at the end
of the function. The value is changed because of the first assignment to *p.
✁
The first and the third assignment are both assignments to *p. Although
nothing has been assigned to p in the meantime, the first assignment writes
to i, while the third one writes to j. The second assignment changes p by
assigning via *r.
These ‘hidden’ assignments are caused by aliases. Immediately after the initialisa-
tion, there are three ways in this program to reach the value stored in i: via i, via
*p, and via **r. Because *p and **r both refer to i, they are called aliases of i.
Aliases cause a problem when one of them is updated. Updating one of the aliases
(for example *p or **r) can cause the value of all the other aliases to change. Up-
dating aliased variables can causes obscure errors, and it is recommended only to
update non-aliased variables.
In a functional language aliases do exist, but the programmer does not have to
worry about them. As values cannot be updated, an alias cannot be distinguished
Revision: 6.37
116 Chapter 4. Structs and Unions
from a copy. Other languages do allow aliases, but some do not allow them to be
overwritten.
Revision: 6.37
4.4. Pointers: references to values 117
The structure containing the data on employee e is modified and returned Trac-
ing the execution of a program increasing the salary of one employee shows an
inefficient usage of memory and execution time:
int main( void ) {
employee e0 = { 13, 20000.0, 1972, 1993 } ;
employee e1 ;
e1 = payrise_ap( e0, 3.5 ) ; e0 : (13,20000.0,1972,1993)
return 0 ; e1 : ( , , , )
}
At Step 1 the function main has initialised the variable e0 with 4 values. The
struct e1 has an undefined value (literally undefined: it can be anything).
employee payrise_ap( employee e, double inc ) {
e.salary = e.salary*(1+inc/100); inc: 3.5
return e ; e : (13,20000.0,1972,1993)
} e0’: (13,20000.0,1972,1993)
e1’: ( , , , )
At Step 2 the function payrise_ap is called with two arguments, the value of e0
and the value 3.5. On the stack, we find the variables of main, and the arguments
of payrise_ap. The value of e is a complete copy of the value associated with
e0 .
employee payrise_ap( employee e, double inc ) {
e.salary = e.salary*(1+inc/100);
return e ; inc: 3.5
} e: (13,20000.0,1972,1993)
e0’: (13, 20000.0,1972,1993)
e1’: ( , , , )
At Step 3 the salary has been modified, and the whole structure is returned. Sub-
sequently, the new record is copied to the variable e1:
int main( void ) {
employee e0 = { 13, 20000.0, 1972, 1993 } ;
employee e1 ;
e1 = payrise_ap( e0, 3.5 ) ; e0 : (13,20000.0,1972,1993)
return 0 ; e1 : (13,20700.0,1972,1993)
}
At Step 4 control has returned to main, and the return value of payrise_ap has
been copied into e1. The inefficiency in execution time arises from the fact that
the record has been copied two times (from e0 to the argument e and from e to
the variable e1). In terms of memory usage, the program is also inefficient: during
Steps 2 and 3, the stack contained three of the database records. The efficiency can
be improved by not passing the structure, but by passing a pointer to the structure
instead and by working on the data directly:
void payrise_im( employee *e, double inc ) {
(*e).salary= (*e).salary * (1+inc/100) ;
}
Revision: 6.37
118 Chapter 4. Structs and Unions
The function payrise_im has two arguments. The first argument e is a pointer
to a employee. The second argument inc is the percentage. The function result
is void, indicating that the function performs its useful work by means of a side
effect. In this case the return statement is usually omitted.
The expression on the right hand side of the assignment statement calculates
the new salary of the employee. The expression (*e).salary should be read
as follows: take the value of the pointer e, dereference it to *e so that we obtain
the value of the whole employee record, and then select the appropriate member
(*e).salary, which is the salary of the employee. This pattern of first derefer-
encing a pointer to a struct and then selecting a particular member of the struct
is so common that C allows more concise syntax for the combined operation:
e->salary.
The assignment causes the new salary value to be written back into the salary
member of the object to which e is pointing. The structure that was passed as the
function argument is updated, this update is the side effect of the function. A truly
idiomatic C function can be created by using the -> and the *= operator (defined
in Chapter 3):
void payrise_im( employee *e, double inc ) {
e->salary *= 1 + inc/100 ;
}
Remember that a *= b is short for a = a * b. To demonstrate the greater effi-
ciency of the use of payrise_im, we give the trace of a main program that calls
the function payrise_im.
int main( void ) {
employee e0 = { 13, 20000.0, 1972, 1993 } ;
payrise_im( &e0, 3.5 ) ; e0 : (13,20000.0,1972,1993)
return 0 ;
}
Control starts in main, and the local variable e0 is initialised. Now payrise_im
is going to be called.
void payrise_im( employee *e, double inc ) {
e->salary *= 1 + inc/100 ; inc : 3.5
} e : Points to e0
e0’: (13,20000.0,1972,1993)
At Step 2 the function payrise_im has been called. Its first argument, e, is not a
copy but a pointer to the employee structure associated with e0 . Consequently,
less storage is needed, and time is also saved because the employee structure has
not been copied.
void payrise_im( employee *e, double inc ) {
e->salary *= 1 + inc/100 ; inc: 3.5
} e : Points to e0
e0’: (13,20700.0,1972,1993)
At Step 3 after the update of e->salary, the value of the variable e0 in the main
program has changed. The pointer e has been used both for reading the old salary,
Revision: 6.37
4.4. Pointers: references to values 119
Exercise 4.7 Reimplement the program of page 102, where a point was rotated
around the origin. Implement it in such a way that
1. The function print_point gets a pointer to a point as its argument.
2. The function rotate gets a pointer to a point as the argument, which
is modified on return. The function should have a return type void.
3. The function main calls rotate and print_point in the appropri-
ate way.
Revision: 6.37
120 Chapter 4. Structs and Unions
type int, so &x is a pointer to an integer, int *, which happens to be the return
type of wrong.
To see what the problem is, the function wrong has to be traced. Upon return-
ing from wrong, a pointer to x is returned, but when the function terminates, all
local variables and arguments of the function cease to exist. That is, the storage
cell that was allocated to x will probably be used for something else. This means
that x no longer exists, while there is still a pointer to x. In this particular pro-
gram, this pointer is stored in y in the main function. Such a pointer to data that
has disappeared is known as a dangling reference or a dangling pointer. The pointer
was once valid, but it is now pointing into an unknown place in memory. Using
this pointer can yield any result; one might find the original value, any random
value, or the program might simply crash.
To understand why a dangling pointer is created, the concept of the lifetime of
a pointer must be explained. Each variable has a certain lifetime. In the case of the
argument of a function, the variable lives until the function terminates. Ordinary
variables that are declared inside a block of statements, for example in the function
body or in the body of a while loop, are created as the block is entered and live
until that block of statements is finished.
The rule to prevent a dangling pointer is: the pointer must have a lifetime that
is not longer than the lifetime of the variable that is being pointed to. In the ex-
ample of the function wrong, the return value of the function lives longer than
the variable x, hence there is a dangling pointer. In the main program that calls
payrise_im on page 118, the variable e0 lives longer than the pointer to it, which
is passed as an argument to payrise_im; therefore, there is no dangling pointer.
In later chapters, we will discuss other forms of storage with a lifetime that is
not bound to the function invocation: dynamic memory (Chapter 5), and global
variables (Chapter 8). With lifetimes that differ, dangling pointers become less ob-
vious. In larger programs it is also difficult to find errors with dangling pointers.
The symptoms of a dangling pointer may occur long after the creation of it. In
the example program wrong, y was used immediately in main. It is not unusual
for a dangling pointer to lay around for a long time before being used. The prob-
lems with dangling pointers are so common that there is a market for commercial
software tools that are specifically designed to help discover such programming
errors.
Revision: 6.37
4.5. Void pointers: partial application 121
✁ ✟
✝ ✝
parabola
parabola ✟ ✟ ✄
The function defines a parabola that has sunk through the X-axis with a depth . ✄
The roots of this parabola are at the points and . (In Chapter 2, we used
✄ ✄
Y-axis
X-axis
✄ ✄
As another, slightly more involved example, one might want to determine a zero
of an even more general parabolic function which has the form below. This
parabola is parametrised over and :
✁
✂ ✡ ✂
✄
✁ ✟
✝ ✝
quadratic
quadratic
✁
✟ ✟ ✟ ✄
In a functional language, there are two ways to solve this problem. The first
solution is to use partial application of functions. The functions parabola or
quadratic can be partially applied to a number of arguments before passing the
result to bisection. To see how this is done, define the SML function parabola
as follows:
(* parabola : real -> real -> real *)
fun parabola c x = x * x - c : real ;
The partially applied version of parabola with the value of c bound to 0.1
is written as (parabola 0.1). This is a function of type real -> real and
therefore it is fit to be supplied to bisection. Examples of evaluation include:
bisection (parabola 2.0) 1.0 2.0 = 1.4140625 ;
bisection (parabola 4.0) 1.0 4.0 = 1.999755859375 ;
Similar to the function parabola, we can write a function quadratic in SML as
follows:
(* quadratic : real -> real -> real -> real *)
fun quadratic b c x = x * x - b * x - c : real ;
To turn quadratic into a function suitable for use by bisection, we need to
partially apply it with two argument values, one for b and one for c. We can now
write the following expressions:
bisection (quadratic 2.0 3.0) 1.0 4.0 = 3.000244140625 ;
bisection (quadratic ˜2.5 8.1) 0.0 100.0 = 1.8585205078125;
Revision: 6.37
122 Chapter 4. Structs and Unions
Revision: 6.37
4.5. Void pointers: partial application 123
a pointer to a function can all be held in a void *. The language only in-
sists that it is a pointer to something. This means that the generalised version,
extra_bisection, can be implemented as follows:
double extra_bisection( double (*f)( void *, double ),
void * x, double l, double h ) {
double m ;
double f_m ;
while( true ) {
m = (l + h) / 2.0 ;
f_m = f( x, m ) ; /* argument x added */
if( absolute( f_m ) < eps ) {
return m ;
} else if( absolute( h-l ) < delta ) {
return m ;
} else if( f_m < 0 ) {
l = m ;
} else {
h = m;
}
}
}
The argument f is a function that must receive two arguments: a pointer to some-
thing and a floating point number. When called, f returns a floating point number.
The extra argument x of extra_bisection must be a pointer to something. This
pointer is passed to the function f in the statement m = f( arg, m ) ;. The C
versions of parabola and quadratic must also be defined in such a way that
they accept the extra arguments:
double parabola( double *c, double x ) {
return x * x - (*c) ;
}
The function parabola has two arguments: a pointer to a double c and a
double x. To obtain the value associated with c, the pointer must be dereferenced
using the asterisk. Note that the parentheses are not necessary; the unary * oper-
ator (pointer dereference) has a higher priority than the binary * (multiplication),
but for reasons of clarity, parentheses have been introduced.
typedef struct {
double b ;
double c ;
} double_double ;
Revision: 6.37
124 Chapter 4. Structs and Unions
Revision: 6.37
4.5. Void pointers: partial application 125
typedef struct {
double b ;
double c ;
} double_double ;
Here is the type to which the variable has to be cast. Therefore (double) 2
✁ ✟
has the same type and value as 2.0. Likewise, the assignment in parabola above
could have been written:
double *c = (double *) p ;
Alternatively, the problem with the incorrect typing in main could have been
solved by calling extra_bisection with:
extra_bisection( (double (*)(void *,double)) parabola,
&c, 4.0, 7.0) ;
/*initialise dd*/
extra_bisection( (double (*)(void *,double)) quadratic,
&dd, 4.0, 7.0 ) ;
Revision: 6.37
126 Chapter 4. Structs and Unions
This explains to the compiler that it should not bother with checking the types
of parabola and quadratic because it is told what the type is intended to be.
Although type-casts can be informative, they do have a serious disadvantage: the
compiler is not critical about explicit type casts. Any type cast is considered to be
legal, hence one could write:
extra_bisection( (double (*)(void *,double)) 42,
&dd, 1.0, 3.0 ) ;
The integer number 42 is cast to the type ‘function with two arguments . . . ’ hence
it is a legal call to extra_bisection with the integer number 42 as a function.
Needless to say, the execution of this program is unpredictable, and most likely
meaningless. Because explicit type casts are unsafe, we discourage their use. In-
stead, we recommend relying on implicit type casts of the language. Although
they are not fool-proof, they are less error prone.
Revision: 6.37
4.6. Summary 127
Exercise 4.8 Rewrite the higher order function sum of Section 2.5.1 so that it is
non-recursive. Use it to implement✎
a program that calculates the number
of different strings with at most letters that you can make with ✟ different
letters. The number of strings is defined by:
✍ ✍✏✎ ✂☎
✟ ✛
✂ ☎
✟ ✂ ✟ ✂ ✏✒✏✕✏ ✂ ✟ (4.2)
You will have to pass ✟ as an extra argument through sum to the function
that is being summed.
Exercise 4.9 Exercise 2.13 required you to approximate given a series. Rewrite it
so that it calculates , for a real number ✟ . The function is given by:
✂
✟ ✛
✝
✎ ✂☎
✛
☎ ✟
✟
(4.3)
✟ ✠
✁
✁
✂
✟ ✟ ✟
✁
✏✒✏✒✏
✂✁ ✄✁ ✪✄✁
✂ ✂ ✂
4.6 Summary
The following C constructs have been introduced:
Data type constructors A struct can hold a collection of values, which are logi-
cally one entity. A union can hold one value of a number of different types
of values. They are defined as follows:
typedef struct typedef union
/*optional
☞
name*/
☞
{ /*optional
☞
name*/
☞
{
✁✄✂ ✂ ; ... ✁✆☎ ☎ ; ✁ ✂ ✂ ; ... ✁✞☎ ☎ ;
} ✁ ; } ✁ ;
☞
Here ✁ and
✟
are the types and names of the members. The struct can op-
✟
case ✄ :
...
default :
}
Revision: 6.37
128 Chapter 4. Structs and Unions
bels match, the statements following the default label, are executed.
✄
✟
The case labels should be unique. To jump to the end of the switch, a
✄
✟
break ; statement should be used, usually each of will end with a break ✟
statement:
case : ✄; break ;
✟ ✟
Void type The type void is used to indicate that a function does not have an ar-
gument or that it does not have a resulting value. The notation void * in-
dicates the type of a pointer to anything.
Implement algebraic data types using structs and unions with a tag to dis-
criminate between the various alternatives of the union. The tag is essential
to distinguish between the various cases of the union.
✁
Passing large data structures around in C is inefficient in space and time. For
good efficiency, the structures should be passed by reference. The disadvan-
tage of passing arguments by reference is that it makes it more difficult to
reason about functions, as they might modify the arguments by a side effect.
✁
The lifetime of data should not be shorter than the lifetime of a pointer to
the data. For example, a function should never return a pointers to a local
variable. Otherwise the pointer will become a dangling pointer, which points
to non-existing space.
✁
Use general higher order functions with partially applied function argu-
ments instead of specialised functions. The mechanism in C supporting par-
tial application is at a lower level of abstraction than the mechanisms avail-
able in a functional language. It requires care to use this mechanism, but
it is nevertheless a useful programming technique that is indispensable for
implementing large scale programs. Examples of its use, for example in the
X-windows library, are shown in Chapter 9. Another use of the void *, to
implement polymorphic types, is also discussed in Chapter 8.
✁
Do not rely on the C compiler to check consistent use of void * type point-
ers. Use the functional version of the solution to check the types carefully.
Revision: 6.37
4.7. Further exercises 129
The use of type casts breaks the type system and thus reduces the opportu-
nity that the compiler has to detect errors in a program. Type casts should
thus be avoided.
Revision: 6.37
130 Chapter 4. Structs and Unions
Exercise 4.11 Design a program that displays the character set of your computer
in a sensible way. Your solution should have a function that classifies a
character. Given a character, the function should return a structure that
identifies the class of the character and some information within the class.
The classes to be distinguished are:
1. A digit. The numerical value of the digit must be returned as well.
2. A lower case letter. In addition the index of the letter (1..26) must be
returned.
3. An upper case letter. In addition the index of the letter (1..26) must be
returned.
4. White space. Nothing extra needs to be returned.
5. Something else.
The rest of your program should call this function for each printable char-
acter and print the character, the numeric equivalent, and the classification
for all of them.
Exercise 4.12 An accounting package needs to calculate net salaries given gross
salaries, and gross salaries given net salaries.
Revision: 6.37
4.7. Further exercises 131
(a) Previously, floating point numbers were used to represent money. This
is not proper, as the figure 1.01 cannot be represented exactly (Sec-
tion 2.4). Design a data structure that stores an amount of money with
a pair of integers, where one integer maintains the whole pounds (dol-
lar, yen, ...), and the other represents the pennies (cent, sen, ...).
(b) Define functions that operate on the money data type. The functions
should allow to add to amounts of money, subtract, and multiply a
sum with an integer, and divide a sum by an integer. The division
should round amounts to the nearest penny/cent/sen.
(c) Define a function that calculates the net salary given the following for-
mula:
✵ ✍ ✱
✒✌ ✪ ✍ if ✌ ✚✪ ✪ ✪
✁
✂ ✠
✌ otherwise
✵ ✍ ✱
if ✌ ✚✪ ✪
☞ ✍
✠
✪
✁
otherwise
✍
✂
☞ if ☞ ✪✚✪ ✪ ✪
✝✞
✍
✞
✠ ✂ ✝ ☛ ✡ ✠
✪✚✪ ✪ ✍ if ✚✪ ✪ ✪ ✚✪ ✪ ✪✚✪
✞
☞ ☞
✁
✞✠
✠ ✂ ✠ ✠ ☛ ✡ ✝
☞ ✤✪✚✪ ✪
✁
otherwise
✝ ✂ ✝
Bounding
Box
Assuming that the edges of the bounding box are parallel to the X and Y
axes, a bounding box is fully specified by a pair of coordinates: the coordi-
nates of the lower left hand corner and the upper right hand corner.
Bounding boxes need to be manipulated. In particular, If two elements of
a drawing have bounding boxes, the combined bounding box needs to be
calculated:
Combined
Bounding Box
Revision: 6.37
132 Chapter 4. Structs and Unions
maximum and coordinates as the coordinates of the top right hand cor-
✟ ✂
✁ ✍ ✟ ✁ ✍ ✟
✝ ✝ ✠
✁ ✍ ✟ ✁ ✍ ✟ ✁ ✍ ✟
✠ ✝ ✝ ☎
Assume in all of the above that the X and Y coordinates are stored in inte-
gers.
Exercise 4.14 Define a struct to represent some of the following 2D objects: cir-
cle, square, box, parallelogram, triangle, and ellipse. Then write a function
that when given a 2D object computes the area of the object.
Exercise 4.15 Using the struct and function of the previous exercise, define an-
other struct that makes it possible to represent 3D objects, such as a
sphere, cube, cone, cylinder and pyramid. Write a function to compute the
volume of the 3D objects.
Revision: 6.37
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 5
Arrays
The C struct of the previous chapter provides the means to collect a small, fixed
number of data items in a single entity. The items may be of different types. The
main topic of this chapter is the array data structure. An array gathers an arbitrary
number of elements into a single entity. The elements of an array must all be of the
same type. Sample applications of arrays are vectors of numbers and databases of
records.
There are other data structures designed to store a sequence of data items of
identical type, the list for example. Although the purpose of lists and arrays is
the same, the data structures have different performance characteristics. For this
reason, some problems must be solved by using lists (arrays would be inefficient),
while for other problems the programmer should choose to use arrays (because
lists would be inefficient).
Although lists and arrays can be expressed both in imperative and in func-
tional languages, the ‘natural’ data structure for both paradigms is different. Han-
dling arrays in a functional language can be inefficient, since a purely functional
semantics does not allow elements of the array to be updated without creating a
new version of the whole array. (We ignore the non-declarative extensions of SML
and sophisticated compiler optimisations designed to detect single threaded use
of arrays). Handling lists in C can be cumbersome, as memory has to be managed
explicitly. For these reasons, problems that do not have a specific preference for
either lists or arrays are generally solved with lists in a functional language, but
implemented in C using arrays. In the next chapter we compare the efficiency of
list and array representations.
This chapter is the first of three that discuss the implementation of sequences
of data. Therefore the present chapter starts with a model to explain how one can
reason about sequences. After that, the representation of arrays in C is explained.
Arrays in C are of a low level of abstraction, that is the programmer has to be con-
cerned with all details of managing arrays. Arrays in C can be used to build pow-
erful abstractions, that hide some of the management details.
While discussing arrays, we present the concept of dynamic memory. This is
needed to implement dynamic arrays and also to implement lists in C, as shown
in Chapter 6.
133
134 Chapter 5. Arrays
✍ ✂ ☎✲✡
✡ ✍ ✡ ✍ ✡ ✍ ✡ ✍
✁ ✂ ✄
✄ ✄
☎
✗✚✪
✂
✡ ☞ ✍ ✡ ✍ ✡ ✍ ✡
☎ ✄ ✆
✄ ✄ ✝ ✄
✯ (5.1)
✁
✡ ✍ ✡ ✍ ✡ ✎ ✍ ✡ ✍
✁ ✝ ✄
✠ ☎ ✝
✗✚✪ ✙
✂
✡ ✍ ✡ ✍ ✡ ✍ ✡
✞ ✄ ☎ ✆
✄ ✄ ✝
✯ (5.2)
✁
✠ ☎ ✄ ✝ ✍
Note that these sequences start with the index 0: they are zero-based. Further-
more, each sequence numbers the elements consecutively. Indeed, sequences can
be generalised to start at arbitrary numbers, to leave ‘gaps’ in the domain, or to
use any other discrete domain. Such more advanced data structures include as-
sociative arrays and association lists. They are beyond the scope of this book; the
interested reader should consult a book on algorithms and data structures, such as
Sedgewick [12].
The advantage of the interpretation of an array as a sequence is that many use-
ful operations on sequences can be expressed using only elementary set theory.
We will discuss the five most important sequence operations in the following sec-
tions. The first three operations, taking the length of a sequence and accessing
and updating an element of a sequence, are used immediately when we define
arrays. The next two operations, concatenating two sequences and extracting a
subsequence from a sequence, are used when discussing lists in Chapter 6.
✟
The length of a sequence is the cardinality of the set of argument/result pairs. We
✟
will denote the cardinality of a set as
.✄
. For the set defined by (5.2), we have
✄ ✄ ✄
the last character of the string (5.1) is . The function to access an element
✄ ✝
will almost always be partial. This means that an undefined value is obtained if
we✁ ✟
try to access an element of a sequence that is outside its domain, for example
✄ (the symbol means ‘undefined’). The notation with parentheses to ac-
cess an element of a sequence is actually used to access arrays in Fortran; in C, one
has to use square brackets to access the element of an array.
Revision: 6.37
5.1. Sequences as a model of linear data structures 135
✁ ✡ ✟ ✡ ✁ ✟ ✢ ✁ ✟ ✡
✗ ☛✛ domain ✬ ✌ ✯ ✗ ✯ (5.3)
✂
✄ ✄ ✄
✁
✄✡
✪
✟
✁
✗✚✪ ✄✡ ✡ ✂ ✡ ✍
☎ ✡ ✍ ✎ ✍ ✂
✡
✙
✍
✞ ✍ ✠✄✡ ☎☎ ✆✡
✄ ✝
✍ ✡ ✍
✄ ✝ ✍ ✯
✄ ✄
✄✡ ✡ ✗✚✪ ✄ ✡ ✍
✪
✍
✂✡ ✡ ✍
✂✡ ✡ ✍
✄ ✄✍ ✁ ✡ ✡
✄ ✄ ✄ ✄
✍ ✎ ✍ ✍ ✍
✙
✂ ✂
✁ ✡
✞ ✄ ✄
✝ ✄ ✝
☞ ✡ ✍ ✡ ✍ ✍
✠ ✠
✁
✄✡
☎
✍
✄✡
☎ ✄
✍
✝✆
✡ ✍
✝✆
✡
✍ ✯
When two sequences are concatenated, we must decide explicitly which one will
be the first part of the resulting sequence and which one will the last part. The
domain of the sequence that will be the last part must be changed. To do this, we
define the operator to denote string concatenation as follows: ✄
✟
✁ ✁ ✁
✄ ✄
✁ ✟ ✡ ✁ ✟ ✢ ✁ ✟
✗ ✛ domain ✯ (5.4)
✂
✄ ✁ ✄ ✂ ✄ ✁ ✁
✄
Applying the concatenation operator to the two strings of (5.2) and (5.1) yields a
proper function:
✁
✗✚✪ ☎✡ ✡ ✍
✂ ✡ ✍ ✡ ✍ ✂
✡ ✍
✠✄ ✄✡ ✡ ✆
✄ ✄ ✄ ✝ ✄
✄
☞ ✍ ✡ ✍ ✍ ✡ ✍
☎ ✝
✁
✡ ✍
✄✂ ✍ ✡ ✍
✪✁
✍ ✁
✎ ✍
✆
✡
✙
✍
✄✡ ✞
✄
✡ ✡ ✍ ✡
✠ ✯
✂
✝ ✄ ✍
The definition of the concatenation operator looks a bit complicated because con-
catenation is actually a complicated operation. When one array is concatenated
with another in a program, at least one of them must be copied to empty cells at
the end of the other. (If there are no empty cells, then both of them need to be
copied!) This notion of copying is captured by the change of domain for the sec-
ond sequence.
Revision: 6.37
136 Chapter 5. Arrays
given that
✍ ✛ ✁✟
domain , then a subsequence
✁
It is also useful to be able to recover a subsequence from a larger sequence. When
✏✒✏✒✏
✄ of a sequence can be ✄
✟ ✄
✄
✁ ✟ ✗ ✁ ✟ ✡ ✁ ✟ ✢ ✛ domain ✁
✄ ✄
✏✒✏✒✏
✏✒✏✒✏
✄
✁
✄ ✄
✟✬
✁
☛ ☛ ✯ (5.5)
The lower bound of the subsequence is , and the upper bound is . The domain
of the resulting subsequence is changed, so that the index of the left most element
of every sequence is always 0.
As an example, we will take the subsequence corresponding to the string “and”
✁ ✟ ✗✚✪✄✡ ✍ ✂✡ ✎ ✍ ✄✡ ✙ ✯
from the string “sandwich”:
✏✒✏✕✏
✂
✄ ✝
We now have at our disposal a notion of sequences based on elementary set the-
ory. Sequences will be used in this chapter to specify a number of operations on
arrays. In the next chapter, we shall look at alternative implementations of se-
quences based on lists.
✗✚✪✄✡ ✡ ✍ ✍
quence:
array(n,v)
✂
✁✌✎ ✟ ✡ ✯
..
.
Revision: 6.37
5.2. Sequences as arrays in SML 137
The second SML function that can be used to create an array is more general:
tabulate(n,f) creates an array of length n corresponding to the sequence:
tabulate(n,f) ✄✡ ✡
✗✚✪
✁
✪
✟✏✍
✂ ✁ ✟✏✍
..
.
✁✶✎ ✟ ✡ ✁✌✎ ✟
✯
access an element that is not within the domain of the array gives an error. The
type of sub is:
(* sub : a array * int -> a *)
Revision: 6.37
138 Chapter 5. Arrays
The function upd copies all elements of the old array s to a new array, except at
index position i, where the new value v is used. The old array s is not changed.
Exercise 5.2 Define an SML function slice(s,l,u) to return the data of the ar-
ray s from index l to index u as a new array. This corresponds to taking a
subsequence.
✁ [ ] ;
Revision: 6.37
5.4. Basic array operations : Arithmetic mean 139
Here ✁ is the type of the elements of the array, is the name of the array and is a
✎
compile time constant (see below) giving the number of elements of the array. An
initialised array declaration has the general form:
✁ [ ] = { , ✂ , ... } ;
✎
The initial values of the array elements ✏✒✏✒✏ ☎ ✂ must all be of type ✁ . The upper-
✎
✁
bound may be omitted from an initialised array declaration, in which case the
number of initial values determines the size of the array:
✁ [] = { , ✂ , ... } ;
✁
It is possible for the explicit number of elements and number of initial values to
disagree. We will say more about this later.
If an array is passed as an argument to a function the type of the array argu-
ment is denoted as follows:
✁ []
Here ✁ is the type of the elements of the array . C does not provide a facility to
discover the number of elements of an array argument. We will discuss this exten-
sively below.
in the array minus 1. For example, if an array has elements, the array bounds are
✎
✪ and .
✂ ☎ ✡ ✂ ✝
✂ ✝
☎ ✂
✄
✁ ✟
✟ ✠
where (5.6)
✟
✄ ✄
All numbers of the sequence are to be added together, and the sum should be di-
vided by the length of the sequence. Here is the SML algorithm to compute the
Revision: 6.37
140 Chapter 5. Arrays
Exercise 5.3 Prove that SML function mean satisfies the specification given by
(5.6).
The SML function mean can be transformed into an efficient C function using the
increasing, left folding for schema of Chapter 3. The resulting, nearly complete C
implementation uses an increasing for loop:
double mean( double s[] ) {
int n = /*length s*/ ;
int i ;
double sum = 0.0 ;
for( i=0 ; i < n ; i++ ) {
sum = sum + s[i] ;
}
return sum/n ;
}
The array argument of the function mean is declared as an array s of doubles:
double s[]
C does not provide an operation to determine the number of elements of an ar-
ray, hence the qualification ‘nearly complete’ for the first C version of mean. The
choice of not providing for a length operation was made for efficiency reasons: not
having to maintain the length of an array saves space and time. However, in the
present case, we need the length of the array. The conventional solution to this
problem is to explicitly maintain the number of elements. In the case of the func-
tion mean, an extra argument n is passed specifying the number of elements of the
array. The definitive version of mean becomes:
double mean( double s[], int n ) {
int i ;
double sum = 0.0 ;
for( i=0 ; i < n ; i++ ) {
sum = sum + s[i] ;
}
Revision: 6.37
5.4. Basic array operations : Arithmetic mean 141
return sum/n ;
}
Working with the number of elements of an array as opposed to the upper bound is
usual in C. It is important to see the difference, as the upper bound is one less than
the length. This is the reason that the for loop in the body of main tests on i < n:
for( i=0 ; i < n ; i++ ) {
✎
The for loop operates over the domain ✪ ☛ ✡ . Indeed almost any for loop
in C that traverses an array has the following form:
✎
for( = 0 ; < ; ++ )
This is opposed to the form:
✎
for( = 1 ; <= ; ++ )
Confusing the number of elements with the upper bound will lead to an ‘off by
one error’, accessing one element too many or too few. This is probably the most
common error amongst programmers fluent in other languages but not used to the
particular way C handles arrays.
To access an element of the array, the notation s[i] is used: s is the name
of the array and i is an expression which is used to index the array. The C ex-
pression s[i] is equivalent to the SML expression sub(s,i). In SML and many
other languages the indexing operator requires the index to be within the bounds
of the array that is being indexed. The C language does not require bounds to be
checked. Therefore, indexing an array with a large or negative index, for example
s[-100], might not result in a run time error, but will probably result in some
random value being returned.
When an array is declared, the number of elements in the array must be speci-
fied, as is shown in the main function used to test the function mean:
int main( void ) {
/* constant 4 used in the next line */
double data[4] = { 55.0, 90.0, 83.0, 74.0 } ;
/* same constant 4 used in the next line */
printf( "%f\n", mean( data, 4 ) ) ;
return 0 ;
}
In this program, data is an array that can store four doubles. The size of an array
must be a compile time constant. This means that the compiler must be able to cal-
culate precisely how long the array is. Any positive integer constant or an expres-
sion using only constants is legal. When an array is declared, it may be initialised,
using the curly brackets as shown in the second line of the function main.
double data[ 4 ] = { 55.0, 90.0, 83.0, 74.0 } ;
The first value of the list, 55.0, will be assigned to the element with index 0, the
next one to the element with index 1, and so on, until the last value (74.0) is as-
signed to the element with index 3. If the number of elements in the list of initialis-
ers exceeds the capacity of the array, a syntax error will result. If fewer elements
are specified in the initialiser list, the remaining elements of the array will be ini-
tialised to zero.
Revision: 6.37
142 Chapter 5. Arrays
The main program for mean above uses the same constant 4 twice: once in the
declaration of the array and once when calling mean. It is tempting to write:
int main( void ) {
const int array_length = 4 ;
double data[array_length] = { 55.0, 90.0, 83.0, 74.0 } ;
printf( "%f\n", mean( data, array_length ) ) ;
return 0 ;
}
Unfortunately, this is invalid C, as array_length is not a compile time constant,
despite the fact that it is obvious that it will always be 4. In general, the keyword
const means that the compiler marks the variables as read-only (that is, they can-
not be changed by the assignment statement) but it does not accept them as con-
stants. For proper constants, we have to use the C preprocessor, which offers a
macro facility. A macro is a means of giving a name to an arbitrary piece of text,
such that wherever the name appears, the piece of text is inserted. The macro def-
inition mechanism uses the keyword #define as follows:
#define array_length 4
5.5 Strings
In the second chapter, we introduced string constants. We postponed discussing
the type of this string constant. Now that arrays have been introduced, the type of
strings and their usage can be discussed in greater detail.
In C, a string is stored as an array of characters. A string constant is denoted by
✎
a series of characters enclosed between double quotes ". If there are characters
✎
between the quotes, the compiler will store the string in an array of
✎
charac-
✂
ters. The first characters hold the characters of the string, while the element with
✎ ✎
index (the -th element, as the first element has index ) holds a special char-
✂ ✪
acter, the NULL-character. The NULL-character, denoted as\0 , signals the end
of a string.
The motivation for ending a string with a NULL-character is that any function
can now determine the length of a string, even though C does not provide a mech-
anism to determine the size of an array. By searching for the NULL-character, the
end of the string can be found. Note that a string cannot contain a NULL-character
Revision: 6.37
5.5. Strings 143
and that an array storing a string must always have one extra cell to accommodate
the\0 . Consider the definition of a function that determines the length of a
string, strlen. This function is defined as follows:
✁✄✂ ☎ ✡ ✟ ✡ ✂☎
strlen ☎
✁ ✟
strlen
✟
✄ ✄
In SML, the definition of strlen just uses the primitive function size:
(* strlen : string -> int *)
fun strlen a = size a ;
In C, we will have to search for the NULL character as follows:
int strlen( char string[] ) {
int i=0 ;
while( string[i] != \0 ) {
i++ ;
}
return i ;
}
Each character of the string is compared with the NULL-character. When the
NULL character is found, the index of that character equals the length of the string
(as defined in the beginning of this section). This function assumes that there will
be a NULL character in the string. If there is no NULL character, the function will
access elements outside the array boundaries, which will lead to an undefined re-
sult.
Revision: 6.37
144 Chapter 5. Arrays
found in the first characters, the function strncmp returns 0, indicating that the
✎
(first characters of the) strings are equal. The number of characters to compare
is passed as the third argument of strncmp. Here are some expressions that use
strncmp:
strncmp( "multiple", "multiply", 8 ) < 0,
strncmp( "multiple", "multiply", 7 ) == 0
Revision: 6.37
5.5. Strings 145
s: "Monkey" : M
o
output : input :
s: M "Monkey" : M
o o
n
The array itself (the pointer) is not changed, it is still the same reference to the
first cell of the array. When returning from the function strcpy, the (pointer to
the) array s is passed to printf to print the copied string. Because the array is
passed as a pointer, an array parameter may also be declared as ✁ *. Here ✁ is the
type of the elements of the array. The first argument of strcpy can be declared
char *output, the second as char *input, this is the notation used in the C-
manual.
Revision: 6.37
146 Chapter 5. Arrays
argc is an integer that tells how many strings are passed to the program. The
value of argc is at least 1.
Revision: 6.37
5.6. Manipulating array bounds, bound checks 147
argv is an array of strings. The first string (with index 0) represents the name of
the program, for example a.out. The other strings are intended as proper
arguments to the program. In total there are argc such arguments. If
argc equals 1, then no proper arguments are provided. The name of the
program, argv[0], is always present.
Here is how argc and argv can be used. The following C program echoes its
arguments, with a space preceding each proper argument:
#include <stdlib.h>
Exercise 5.4 Write a program that simulates the following game of cards. Player
A holds three cards on which four numbers are printed as follows:
✂ ✂ ✁
✝ ✠
✠ ✝ ☎ ✝ ☎ ✝
Revision: 6.37
148 Chapter 5. Arrays
2. Bound checks are not enforced by the language. Functions must test explic-
itly on the bounds of arrays.
This is also the case in SML, and even in our sequences. There are however other
programming languages in which the lower bound of an array can be chosen
freely; Pascal is an example. The choice to give only rudimentary support for
arrays in C was made to improve execution speed. Programming convenience
has been sacrificed for sometimes significant improvements in execution speed.
This section shows how to use arrays with a lower bound that is not fixed to zero,
and how to insert the proper bound checks. Section 5.7 will show a more flexible
way to declare arrays, overcoming the limitation that the size of an array must be
known at compile time.
As an example of using flexible array bounds we will implement a program
that makes a histogram. A histogram is a bar graph that shows how often each
value occurs in a sequence of values. Given a string, a histogram can be used to
illustrate how often each character occurred in that string. The figure below shows
the histogram of the string “abracadabra”.
5
2 2
1 1
a b c d e f g h i j k l m n o p q r
✁ ✟ ✁ ✟ ✡ ✎ ✢ ✁ ✟ ✎ ✢ ✁ ✟
histogram ord range (5.7)
✟
✄ ✗ ✟ ✔✛
✟ ✄ ✬ ✗ ✄ ✟ ✯ ✯
Here we assume that the function ‘ord’ maps characters to natural numbers. In
SML, we are going to give a function histogram that counts characters in a par-
ticular range. The bounds of this range will be supplied as arguments. An array of
characters (represented in SML as an array of strings) is analysed, and the number
of times that each character is found is returned in an array indexed (indirectly)
Revision: 6.37
5.6. Manipulating array bounds, bound checks 149
with characters. A useful auxiliary function is inc below: it delivers a new ar-
ray, which is the same as the old one, except that a given array element has been
incremented by one:
(* inc : int array -> int -> int array *)
fun inc s i = upd(s,i,sub(s,i) + 1) ;
The histogram function (below) starts by calculating the length n of the array
input. The library function array creates a new array empty and initialises each
element to 0. The size of the new array is one more than the difference between
the upper bound u and the lower bound l. The tally function is then folded
over the index domain of the array input, calling inc to tally the occurrences of
each character in the input array input. The primitive SML function ord converts
the first character of a string to its integer code.
(* histogram : char array -> int -> int -> int array *)
fun histogram input l u
= let
val n = length input
val empty = array(u - l + 1,0)
fun tally hist i = inc hist (ord(sub(input,i))-l)
in
foldl tally empty (0 -- n-1)
end ;
Exercise 5.5 Prove that the function histogram satisfies the specification of
(5.7)
As was shown in the previous section on strings, C does not allow arrays to be
returned as function values. Therefore, we will define the function histogram in
such a way that the array in which the histogram is going to be written is passed
as an argument. This argument is passed by reference because it is an array. This
is the same method as used for the first argument of strcpy.
void histogram( char input[], int hist[], int l, int u ) {
int i ;
/*C Make an empty histogram*/
for( i=0 ; input[i] != \0 ; i++ ) {
/*C Incorporate input[ i ] in the histogram*/
}
}
The first argument contains the text for which a histogram is to be made. Since
the string is NULL terminated, the size of the array does not have to be passed as
an argument. The for loop that iterates over the array stops as soon as the NULL
character is recognised. The second argument is the array that will contain the
histogram, and the third and the fourth arguments are the bounds of this array. To
show how this function is going to be used, consider the following fragment of a
Revision: 6.37
150 Chapter 5. Arrays
main program:
#define lower a
#define upper z
#define number (upper - lower + 1)
Revision: 6.37
5.6. Manipulating array bounds, bound checks 151
Revision: 6.37
152 Chapter 5. Arrays
#define lower a
#define upper z
#define number (upper - lower + 1)
Exercise 5.6 In the final histogram program above, there are seven places where
an array is being indexed (on the five lines marked with the comment
/*Exercise 5.6*/). Before one of these indexing operations, a bound
check is performed. Why are there no bound checks before the other six
index operations?
To complete the discussion, let us consider briefly how the program would exe-
cute. Just prior to the calls to histogram, the array hstgrm contains undefined
values. As shown previously, a pointer to the array is passed to the function
histogram, allowing it to update the histogram. This will ultimately lead to a
complete histogram. When the function histogram is about to return, the array
hstgrm will have been updated to contain the following values:
hist :
hstgrm : 5
2
1
1
0
1. The array size has to be fixed statically. In the example of the histogram,
it was assumed that the string had only lowercase letters: the bounds were
fixed ata andz .
Revision: 6.37
5.7. Dynamic memory 153
Revision: 6.37
154 Chapter 5. Arrays
da.ub = u ;
da.data = calloc( u - l + 1, sizeof( int ) ) ;
if( da.data == NULL ) {
abort() ;
}
return da ;
}
The lower and upper bounds l and u are arguments of the function
array_alloc. The function stores the bounds in the dynamic_array structure
and calls calloc to allocate the store for the zero-based array. To obtain the size of
an element, C supports a built-in function sizeof, which returns the size in bytes
of an element of the specified type. The following expression returns the number
of bytes needed to store an integer:
sizeof( int )
The number of bytes needed to store a dynamic_array structure is returned by
the expression:
sizeof( dynamic_array )
This amount of store would cover one pointer and two integers. The store al-
located by calloc is guaranteed to be filled with zero-values. The result of
array_alloc is a proper dynamic array.
The function calloc will normally return a pointer to a block of store that can
hold the required data. In exceptional situations, when the system does not have
sufficient space available, the function calloc will return a special pointer known
as the NULL-pointer. No valid pointer ever has the value NULL. By testing if the
value of da.data equals NULL, we can established whether the function calloc
has succeeded in allocating the memory. The program is aborted if calloc has
run out of space. Testing the value coming from calloc is recommended, as con-
tinuing a computation with a pointer that might be NULL will give random results:
the program might give an arbitrary answer or might crash.
The function array_alloc shows that a dynamic array consists of two
parts: the zero-based array, which holds the actual data; and the struct of type
dynamic_array, which holds the bounds and a pointer to the zero-based array.
These are separate data structures, which makes it possible to manipulate them
separately. There is a danger in this, as one might think that just declaring a vari-
able of type dynamic_array creates a proper dynamic array:
dynamic_array x ; /* WRONG */
This does not work, as the declaration merely creates the space necessary to hold
the pointer and the bounds. They are not properly initialised, nor is the zero based
array allocated. In terms of the model of the store, the result of this declaration is:
x:
Revision: 6.37
5.7. Dynamic memory 155
The two fields storing the upper and lower bound are undefined, and the third
field, which should point to a zero-based array, is also undefined. The correct way
to use the dynamic array facility is by declaring and initialising the dynamic ar-
rays:
dynamic_array x = array_alloc( l, u ) ;
The function array_alloc will return an initialised dynamic array structure.
The first and second field will be initialised to l and u (representing the desired
lower and upper bounds of the array), and the third field is allocated with a zero-
based array of ✂ elements. Here is a picture showing the results:
x: 0
l 0
u
Revision: 6.37
156 Chapter 5. Arrays
Exercise 5.7 Adapt the SML version of histogram so that it uses a dynamic ar-
ray.
Revision: 6.37
5.7. Dynamic memory 157
This demonstrates the advantage of using the ++ operator. The expression specify-
ing which element is to be incremented occurs twice in the second statement, but
only once in the first. This means that if the expression needs to be changed for
some reason, it would require a consistent change of both copies of the expression
in the latter form, while one change suffices for the first form.
The main function using dynamic arrays is shown below.
int main( void ) {
dynamic_array hist = histogram( "abracadabra", 11 ) ;
int i ;
for( i = hist.lb ; i <= hist.ub ; i++ ) {
printf( "%c: %d\n", i, hist.data[ i - hist.lb ] ) ;
}
return 0 ;
}
An execution trace shows how the dynamic array is used. Immediately after the
function array_alloc is called, the dynamic array structure is filled with the fol-
lowing values:
data: 0
lb: a
ub: a
The upper bound and the lower bound are botha , and the data is a pointer to
a single integer, which stores the value 0. The for loop is entered, and after the
first iteration of the for loop the first character of the string has been tallied, which
results in the following histogram:
data: 1
lb: a
ub: a
The bounds are unchanged, the element corresponding toa in the histogram
has been incremented to reflect the singlea that has been counted. In the next
iteration of the for loop, the characterb is encountered. The valueb is higher
than the upper bound; therefore, the dynamic array has to be extended. Immedi-
ately after the call to extend, the histogram will look as shown below. The upper
bound is nowb , the lower bound has not changed, and the histogram now con-
sists of two integers. The first one is still 1, the second one has been initialised to
0.
Revision: 6.37
158 Chapter 5. Arrays
data: 1
lb: a 0
ub: b
After the second iteration of the for loop, the new array element has been incre-
mented:
data: 1
lb: a 1
ub: b
The third character of the input isr . This is higher than the upper bound, so
the dynamic array has to be extended again. This time, the extension results in the
following histogram:
data: 1
lb: a 1
ub: r 0
...
0
The bounds have been set toa andr , and the zero-based array now contains
18 integers which store the histogram values fora up tor . The first two el-
ements of the histogram have been copied from the previous version (they have
both value 1). The other 16 cells of the array have been initialised to zeroes.
The rest of the input can now be processed, without extending the histogram,
because all characters of “abracadabra” are in the rangea . . .r . The end re-
sult of the function histogram is the following:
data: 5
lb: a 2
ub: r 1
1
0
...
0
2
Revision: 6.37
5.7. Dynamic memory 159
✪ ✝
data: 1
lb: a
ub: b 1
0
Destroying the only pointer to an area of store causes the area to become inacces-
sible. In C such an inaccessible area of store will not be reclaimed automatically.
All functional languages have a garbage collector, and also some modern imper-
ative languages such as Java, Modula-3 and Eiffel have a garbage collector. This
is a component of the language implementation that detects when an area of the
store is not longer accessible so that it can be reused. Unfortunately, C and C++ do
not have garbage collectors, so the programmer must assume full responsibility
for the management of the store.
When inaccessible areas of store are not reclaimed, a program may eventually
run out of available space. This situation is known as a memory leak because store
seems to disappear without warning.
The inaccessible store of the old dynamic array must be deallocated explicitly
so that it can be reused later. This deallocation can be performed by calling the
function free, with the pointer to the block of store to be freed as an argument.
The function free will mark the block for reuse by calloc at a later stage.
The following call thus deallocates the block of store that was referenced from
hist.data:
free( hist.data ) ;
The new inner part of the for loop of the function histogram would read:
dynamic_array new ;
if( input[i] < hist.lb ) {
new = extend( hist, input[i], hist.ub ) ;
free( hist.data ) ;
hist = new ;
Revision: 6.37
160 Chapter 5. Arrays
Revision: 6.37
5.7. Dynamic memory 161
Revision: 6.37
162 Chapter 5. Arrays
more. This is a vicious circle. If the pointer is destroyed before the memory is
deallocated, we have a memory leak, while if the memory is deallocated before
the pointer is destroyed, we have a dangling pointer. This is resolved by first deal-
locating the memory and destroying the pointer shortly afterwards.
Other languages, like C++, do have support to deallocate memory at the mo-
ment that pointers leave the scope. More advanced object oriented languages have
built-in garbage collectors that completely relieve the programmer of the burden
of managing the memory.
Revision: 6.37
5.8. Slicing arrays: pointer arithmetic 163
✁✄✂ ☎ ✡ ✆✞✂ ☎ ✡ ✟ ✡ ✂☎
search ✁ ✁
✁ ✍ ✟ ✢
search ✁ min ✗ ✁ ✂ ✁ ✁✄✂ ✁✁ ✯
✟
✄ ✄
✞ ✞
If the word does not appear in the text ✁ , the function min is asked to calculate
✞
the minimum of an empty set. In this case we shall assume that the number is
returned.
To give a search algorithm let us assume that we first try to match the word
with the text, starting at the first position of the text. If this match fails, then we
move on to the second position and see if we can match the word there. If the
word occurs in the text then we will ultimately find it, and return the present
starting position. Otherwise, we will exhaust the text, and return . Here is a
schematic rendering of the first three steps of the search process, where we have
chosen a word of length :
✂
✁ ✁ ✂ ✁ ✁ ✁
✆ ✄
...
Step 1:
✁
✞ ✁ ✞ ✂ ✞
✁ ✁ ✂ ✁ ✁ ✁
✆ ✄
...
Step 2:
✁
✞
✁
✞
✂ ✞
✁ ✁ ✂ ✁ ✁ ✁
✆ ✄
...
Step 3:
✁
✞ ✁ ✞ ✂ ✞
The implementation of the search algorithm in SML takes as arguments the word
w and the text t. Both the word and the text are represented as an array of char-
acter. The function search also takes the current position i as argument. This
indicates where a match is to be attempted:
(* search : char array -> char array -> int -> int *)
fun search w t i
= if i > length t - length w
then ˜1
else if slice (t,i,(i+length w-1)) = w
then i
else search w t (i+1)
Revision: 6.37
164 Chapter 5. Arrays
The function search is tail recursive. The main structure of the C version of
search is therefore easily written:
int search( char *w, char *t, int i ) {
while( true ) {
if( i > strlen( t ) - strlen( w ) ) {
return -1 ;
} else if( /*slice of t equals w*/ ) {
return i ;
} else {
i = i + 1 ;
}
}
}
Apart from some inefficiencies, which we will resolve later, we need to fill in how
to take the slice of t. The inefficient way to make a slice would be to allocate a
block of memory that is large enough to hold the slice, and to copy the relevant
cells of the array t into this new block of memory. C does however offer an alter-
native that does not require any cells to be copied. Instead, a different view on the
same array is created.
To explain how this works, let us consider a simple example first. Here is a
small main program with an array q of six characters. The array is initialised with
the string "Hello":
int hello( void ) {
char q[6] = "Hello" ;
char * r = q+2 ;
return r[0] == q[2] ;
}
As explained before, q is really a constant pointer to the first of the six characters.
Now comes the interesting bit: in C it is permitted to add an integer to a pointer,
such that the result can be interpreted as a new pointer value. This is shown by
the statement r = q+2. The figure below shows the array q, and it also shows the
array r, which is merely another view on q.
q: H H
e e
✁ ✟
l r: l r[0] (q+2)[0]
l l
o o
✁ ✟
\0 \0 r[3] (q+2)[3]
The expression (q+2), which ‘adds’ 2 to the array q, points to q[2]. Thus, (q+2)
is an array where the element with index 0, (q+2)[0], refers to q[2]. The ele-
ment with index 3, (q+2)[3], is the same element as the last element of q, q[5].
Revision: 6.37
5.8. Slicing arrays: pointer arithmetic 165
So the highest index that can be used on the array (q+2) is 3. The elements that
are shaded are still there, but they reside below the array. They can be accessed
using negative indices. Thus, the expression (q+2)[-2] refers to the lowest ele-
ment of the array q. The bounds of this array are -2 and 3, and the valid indices
for (q+2) are -2, -1, 0, 1, 2, and 3. Any other index is out of bounds and will,
because of the absence of bound checks, have undefined results.
The array (q+2) is almost a slice q(2 . . . 5) of the array q. There are two dif-
ferences:
1. (q+2) and q share the values of their cells. This is more efficient; but if either
is updated, the other one is updated.
2. (q+2) still refers to the whole array q, the lower and upper parts are not
physically detached.
Note that the picture above only suggests that q and q+2 are different arrays,
whereas in reality they share the same storage space.
Returning to the word search function, how can we use pointer arithmetic to
create a slice of the text and do so efficiently? Firstly, the sharing implied by
pointer arithmetic is not a problem because the program that searches for a string
does not update either the array or the slice. The second problem from the enu-
meration above is a real one: the comparison between the slice and the array w
must only compare the section of the slice with indexes ✏✒✏✒✏ ; it should ig-
✟
✪ ✞
nore any cells below or above that range. The solution to this problem is to use
the function strncmp, which compares only a limited part of an array. Calling
strncmp with the start of the slice, and with the number of characters to compare
will give the desired result. Consider the following test:
strncmp( t+i, w, strlen(w) ) == 0
The expression t+i results in a new view on t, shifted by i cells. So, as far as
the function strncmp is concerned, the first element to compare is element t[i].
Since strncmp does not look below this index, it does not matter that the rest of
the array is still there. Because no more than strlen(w) characters will be com-
pared, the upper part of t will also be ignored.
Before giving the complete code of search, we remove some inefficiencies.
Observe that on every iteration of the while loop, the function strlen is called to
calculate the lengths of w and t. As these two lengths do not change, it is more ef-
ficient to calculate each length only once, and to store the result in a local variable.
This leads us to the following complete and efficient implementation of search:
int search( char *w, char *t, int i ) {
const int length_w = strlen( w ) ;
const int length_t = strlen( t ) ;
while( true ) {
if( i > length_t - length_w ) {
return -1 ;
} else if( strncmp( t+i, w, length_w ) == 0 ) {
return i ;
Revision: 6.37
166 Chapter 5. Arrays
} else {
i = i + 1 ;
}
}
}
A sample main function to test the function search follows below. It assumes
that the word is passed as the first argument to the program and that the text is
the second argument.
int main( int argc, char *argv[] ) {
if( argc != 3 ) {
printf( "Wrong number of arguments\n" ) ;
return 1 ;
} else {
if( search( argv[1], argv[2], 0 ) != -1 ) {
printf( "%s occurs\n", argv[1] ) ;
} else {
printf( "%s does not occur\n", argv[1] ) ;
}
return 0 ;
}
}
The process of searching something in a string is so common that the C library
provides standard functions for searching in strings. For example, the function
strstr implements the function search above. The function has a different in-
terface. It has only two parameters, and they are in reverse order: the text comes
first, followed by the word. The return value is also different, instead of returning
the index of the element where the string was found, strstr returns a pointer to
that element. So the return type of strstr is char * and not int. Instead of re-
turning the index i, the function strstr returns the expression s+i, and instead
of returning the integer -1 on a failure, strstr returns the constant NULL.
Here is a revised version of the main program above that uses strstr instead
of our own function search:
int main( int argc, char *argv[] ) {
if( argc != 3 ) {
printf( "Wrong number of arguments\n" ) ;
return 1 ;
} else {
if( strstr( argv[2], argv[1] ) != NULL ) {
printf( "%s occurs\n", argv[1] ) ;
} else {
printf( "%s does not occur\n", argv[1] ) ;
}
return 0 ;
}
}
Revision: 6.37
5.8. Slicing arrays: pointer arithmetic 167
Firstly it prints the difference between r and q. They both point in the same
array of characters, and as q points to the beginning, r-q is equal to the in-
dex of where r was pointing to, 7.
✁
Secondly it prints the difference between s and q. This equals the index of
the word “world” in q, which is 17.
✁
The third number is s-r, both are indices in the array pointing to q, the sub-
traction will result in 10, as s is pointing to q[17] and r is pointing to q[7].
The arithmetic with pointer works just like ordinary arithmetic as long as the point-
ers remain within the array bounds. The single exception being that a pointer that is
pointing just above the array is allowed, although this pointer should not be deref-
erenced. A number of interesting observations can now be made:
✁
Revision: 6.37
168 Chapter 5. Arrays
pointer to the middle. Care should be taken however when aliases are created with
pointer arithmetic, for example, when two slices (partially) overlap. As explained
in Section 4.4.2, aliases are a problem when they are updated.
Pointer arithmetic works with arrays that are allocated dynamically in exactly
the same way as with arrays that are declared in the program. Pointer arithmetic
can also be used in conjunction with assignment statements. For example, if x is a
pointer to some (part of) an array, then the operation x++ will cause x to point to
the next cell. Again, this operation can be applied only as long as x points to the
same data. To conclude this section we will show an idiomatic C function, which
is used to copy a string:
void copystring( char *out, char *in ) {
while( *out++ = *in++ ) {
/* Nothing more to do */
}
}
The while loop has a complicated conditional that contains three assignments. The
first assignment is the = sign, it assigns one character from the in array to the out
array. The other two assignments are the ++ operators, which shift both in and
out one position further. To finish it off, the loop terminates if the character that is
copied happens to have the same representation of false, that is the\0 char-
acter. We do not recommend to use this style of code, but you will find this style
of coding in real C programs.
Revision: 6.37
5.9. Combining arrays and structures 169
fun mean s
= let
val n = length s
fun add_element sum i = sum + real(sub(s,i))
in
foldl add_element 0.0 (0 -- n-1) / real(n)
end ;
typedef struct {
char name[n_name] ;
Revision: 6.37
170 Chapter 5. Arrays
double salary ;
int year_of_birth ;
int sold[n_sales] ;
} employee ;
Revision: 6.37
5.9. Combining arrays and structures 171
1. Pass an extra array to the function increase that serves as the output array.
This is the way the function histogram was implemented previously.
2. Decide that the old array will no longer be necessary and reuse this array to
pass the result back. This is the way that the problem with update_single
was solved.
Revision: 6.37
172 Chapter 5. Arrays
Here is the second solution to implementing increase. This version destroys the
old database.
void increase( personnel old, int n ) {
int i ;
for( i=0 ; i<n ; i++ ) {
update_single( old, i ) ;
}
}
Both implementations of increase are valid, but they have different semantics.
The first produces a new, amended version of the database; the second version
overwrites the old one. The first one is useful if the company keeps a backup of
the database, whereas the second one is more efficient if the old one is no longer
needed. Which version is best depends on knowledge about the intended use of
the program.
The C functions needed to handle the database are shown completely below:
double mean( int s[], int n ) {
int i ;
int sum = 0 ;
for( i=0 ; i < n ; i++ ) {
sum = sum + s[i] ;
}
return (double)sum/n ;
}
Revision: 6.37
5.9. Combining arrays and structures 173
the program consistent, the function payrise has also been replaced by its de-
structive counterpart. It is also worthwhile to notice that the function mean has
been changed slightly. The function mean of the beginning of this chapter calcu-
lated a value of the type double, when given an array of values of type double.
The current version of mean has an array with integers as an argument, but it re-
turns a double. Somewhere along the line types must be coerced. We have cho-
sen to perform the type coercion when returning the value:
return (double)sum/n ;
It would have been equally valid to sum the values in a double precision sum and
to perform the typecast when adding elements to the sum.
To complete the program, a significant amount of extra code is needed to per-
form the output. The functions print_personnel and print_employee print
the database and its records respectively:
void print_employee( employee e ) {
int i ;
printf( "%s, %f, %d, [ ", e.name, e.salary,
e.year_of_birth ) ;
for( i=0 ; i<n_sales ; i++ ) {
printf( "%d ", e.sold[i] ) ;
}
printf( "]\n" ) ;
}
Revision: 6.37
174 Chapter 5. Arrays
call by reference for passing the structure as a parameter. The result is a more effi-
cient function:
void print_employee( employee *e ) {
int i ;
printf( "%s, %f, %d, [ ", e->name, e->salary,
e->year_of_birth ) ;
for( i=0 ; i<n_sales ; i++ ) {
printf( "%d ", e->sold[i] ) ;
}
printf( "]\n" ) ;
}
The call to the function print_employee is changed accordingly:
print_employee( &p[i] ) ;
Although more efficient, this implementation is less clear, because a user of the
function print_employee cannot be sure that the structure is not changed any-
more.
#define ROW 3
#define COL 5
typedef int matrix[ROW][COL] ;
A multi-dimensional array can be initialised by listing the desired values for each
✠ matrix we would list the desired values for the first
✆
row ( ✄ ✏✕✏✒✏ ), then those for the second row ( ✏✒✏✒✏ ) etc:
✍ ✍ ✍ ✍
✝ ✝ ✝ ✝
matrix m = { {11,12,13,14,15},
{21,22,23,24,25},
{31,32,33,34,35} } ;
A multi-dimensional array with fixed bounds can be passed to a function. Here is
a function to print a matrix:
void print_matrix( matrix m ) {
int i,k ;
for( i = 0; i < ROW; i++) {
printf( "row %d:", i ) ;
for( k = 0; k < COL; k++) {
printf( "\t%d", m[i][k] ) ;
}
printf( "\n" ) ;
Revision: 6.37
5.11. Summary 175
}
}
The expression m[0][0] refers to the top left element, and the expression
m[ROW-1][COL-1] refers to the bottom right element.
If the boundaries of the dimensions in a multi-dimensional array cannot be de-
termined statically, one should use an array containing pointers to further arrays.
Consult Kernighan and Ritchie for a further elaboration of this point [7, Page 110].
5.11 Summary
The following C constructs were introduced:
Array types Array types are denoted using square brackets. Static arrays must
have a compile time constant size. Dynamic arrays can be created using the
function calloc, which has two parameters, the size of an element and the
number of elements to allocate, it returns a pointer to the first element of the
array.
Array index Arrays are indexed with square brackets: [ ], where is the ar-
ray and is the (integer) index. The first element of an array has index
0. The same notation can be used to identify an element to be overwritten.
[ ] = overwrites the -th element of with .
Array bounds C does not provide array bound checking. It is wise to either be
able to argue that array bounds cannot be exceeded or put explicit checks in
a program to make sure that the bounds are not exceeded.
Arrays as function arguments and result In C, an array is not a set of values, but
a pointer to the set of values. The consequence is that when passing an ar-
ray as an argument, the array is passed by reference. Arrays should not be
returned as a function result, as only the pointer is returned, and the set of
values might cease to exist when the function terminates. Arrays allocated
explicitly can safely be passed around.
An array is a sequence, that is, a mapping from natural numbers to some set
of values.
✁
Working with arrays gives rise to a common programming error, the off by
one error; it is particularly easy to overrun the upper bound of an array by
mistaking the length for the upper bound.
Revision: 6.37
176 Chapter 5. Arrays
Beware of memory leaks, that is, use free on all data structures allocated by
calloc.
✁
Make sure that a data structure is truly redundant before freeing it. Using
free too early results in a dangling pointer.
✁
Attempt to deallocate memory in the same place where the last reference to
a block is destroyed. This results in neat functions.
✁
Use void returning functions if the function does its useful work by a side
effect. Such functions are often called procedures. Do not use side effects
in proper functions. This ensures that functions and procedures are always
clearly identified.
✁
When side effects are necessary for efficiency reasons, try to hide them in an
auxiliary function and provide a functional interface to the outside world.
Many of the sequences in this chapter that have been implemented using arrays
could have been implemented with lists instead. The next chapter discusses the
implementation of lists in C. It also compares the two implementations of se-
quences from the point of view of their efficiency.
Exercise 5.9 Rewrite your card simulation program from Exercise 5.4 so that it
plays a real game. When playing, the user should choose a secret number
and the program should try to ‘guess’ it. Here is sample dialogue with such
a program. The input of the user is underlined; the text produced by the
program is in plain font.
Exercise 5.10 In this exercise, create a function that prints a table of the months
of a year with the number of days in each month. Furthermore, write a
function that when given a particular date in a year calculates what date it
will be exactly one week later.
Revision: 6.37
5.12. Further exercises 177
Exercise 5.11 The bisection method of the previous chapters can be used not only
to search for the roots of a function but it can also be used to search effi-
ciently for some special item in non-numerical data.
(a) Modify extra_bisection from Section 4.5 to work with discrete
rather than continuous data. That is replace all types double by int
and reconsider the use of eps, delta and absolute.
(b) Write a main function to call your discrete bisection function such that
it will search for an occurrence of a string in a sorted array of strings.
Use the arguments to your a.out to search for another occurrence of
a.out to test your code. For example, the following command should
produce the number 5, and it should make only two string compar-
isons.
a.out a.1 a.2 a.3 a.4 a.out b.1
(c) Generalise the bisection function such that it can assume the role of the
discrete as well as the continuous bisection. Then rewrite your pro-
gram to use the generalised bisection rather than the discrete.
Revision: 6.37
178 Chapter 5. Arrays
Exercise 5.12 If you use a machine with a UNIX like operating system you might
try to write the following program that determines how much address
space your program is allowed to use. A standard UNIX function, brk, at-
tempts to set the highest address that your program may use (the ‘break’).
The function returns either 0 to indicate that the break was set successfully,
or 1 to signal that the break could not be set that high. Use the bisection
function of the previous exercise to find the highest break that you can use
(assume that your system allows pointers and integers to be mixed, and
that the break is somewhere between 0 and 0x7FFFFFFF).
Exercise 5.13 A program is needed to make sure that the exam results of the first
year Computer Science students at your University are correctly processed.
(a) Define a two dimensional array for storing the exam results (inte-
gers in the range ✪ ✏✕✏✒✏ ✪✚✪ ) of the first year Computer Science students.
Make sure that you choose appropriate upper bounds on the array(s)
for the number of students in your class and the number of modules in
your course.
(b) Write a function to compute the sum of all the scores in the exam re-
sults table for a particular student and another function to count the
number of non-zero scores for a particular student.
(c) Write a function to print the exam results as a nicely formatted table,
with one line per student and one column per module:
✁
At the end of each row, print the sum, the number of non-zero
scores and the average of the scores in the row.
(d) Write a main function to create a table with some arbitrary values.
Then call your print function on the table.
(e) What measures have you taken to make sure that the exam results are
processed and averaged correctly?
Exercise 5.14 A particular spread sheet program has the following features:
(a) The program knows about a fixed maximum number of work sheets.
(b) Each work sheet has a name (of a fixed maximum length), and a date
and time of when it was last used.
(c) Each work sheet is a rectangular 2-dimensional array of cells with
bounds that will not exceed certain fixed maximum values.
(d) The are four kinds of cells: a formula (represented as a string of a fixed
maximum length), an integer, a real number, or a boolean.
(e) The spread sheet program should be able to tell what kind of cell it is
dealing with.
(f) Each cell has a flag stating whether it is in use or not.
Design the C data structures required to support all these features by giving
#define and typedef declarations. Do not write the rest of a program that
might use the data structures.
Revision: 6.37
5.12. Further exercises 179
Exercise 5.15 A magic square [8] of order is a square in which the numbers
✏✒✏✕✏
✍ ✎
The sum of the numbers in each of the two main diagonals is the same.
✁
✝ ✠
✝ ✂
✏✒✏✒✏
✍ ✍
✠ ✝
Revision: 6.37
180 Chapter 5. Arrays
Revision: 6.37
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 6
Lists
181
182 Chapter 6. Lists
Revision: 6.34
6.1. Lists of characters 183
list_struct. The two assignment statements initialise the head and tail fields
of the data structure.
Every list must be properly terminated so that functions operating on a list can
find out where the list ends. The C convention for indicating the end of any list
or recursive data structure is to use a null-pointer, denoted NULL. The SML value
list_hi shown above can be implemented in C as follows:
char_list list_hi( void ) {
char_list hi = cons( H, cons( i, NULL ) ) ;
return hi ;
}
When executed, the function list_hi creates the heap structure shown below:
hi: H
i
✁
NULL value.
Revision: 6.34
184 Chapter 6. Lists
}
return l->list_tail ;
}
Often C programmers tend not to use functions for head and tail, but to inline the
code directly, using l->list_head and l->list_tail. For two reasons inlin-
ing is slightly more efficient. Firstly, the function call overhead is avoided. Sec-
ondly, the test on the end of the list is avoided (l == NULL), which is safe if the
programmer ‘knows’ that the list is not empty, similar to avoiding bound checks
on arrays, see Chapter 5. The disadvantage of inlining is however that the struc-
ture of the data is exposed in several places in the program. It will be impossible
to hide implementation details, a feature which will be discussed in Chapter 8.
Therefore, it is better to leave inlining to the compiler.
Exercise 6.2 The SML data type char_list above represents an algebraic
data type, with recursive use of the type char_list. The C version
char_list shown above uses the conventional C notation NULL to denote
the end of the list, instead of using an equivalent of the SML equivalent
Nil. Show how the C constructs union, enum, and struct can be used to
create a literal equivalent in C of the algebraic data type char_list (see
also Chapter 4)
The following sections will see a graded series of example problems that use lists.
This will create, amongst others, the implementations of the following functions:
Revision: 6.34
6.2. The length of a list 185
Exercise 6.3 Write a tail recursive version of length in SML and give the corre-
sponding C implementation that uses a while-statement.
Revision: 6.34
186 Chapter 6. Lists
Exercise 6.4 When trying to access a non-existent list element, the SML definition
of nth raises the exception Match, but the C implementation of nth pro-
duces an undefined result. Modify the C version of nth so that it aborts
with an error message when a non-existent list element is accessed.
Revision: 6.34
6.4. Append, filter and map: recursive versions 187
Exercise 6.5 Rewrite the recursive definition of append to use list primitives and
conditionals rather than pattern matching.
The function append can be translated into a recursive C equivalent as shown be-
low. How to create a version that uses a while-loop is discussed in Section 6.5:
char_list append( char_list x_xs, char_list ys ) {
if( x_xs == NULL ) {
return ys ;
} else {
return cons( head( x_xs ),
append( tail( x_xs ), ys ) ) ;
}
}
When applied to the two argument lists, the append function copies the first list
and puts a pointer to the second list at the end of the copy. The copying is nec-
essary, as append cannot be sure that the first argument list is not going to be
needed again. To illustrate this important point, consider a concrete example of
using append. This is the function list_hi_ho below. It appends the two lists
hi and ho into the result list hiho.
char_list list_hi_ho( void ) {
char_list hi = cons( H, cons( i, NULL ) ) ;
char_list ho = cons( H, cons( o, NULL ) ) ;
char_list hiho = append( hi, ho ) ;
return hiho ;
}
Executing the function list_hi_ho creates the two heap structures shown be-
low:
hi: H
i
✁
hiho: H
i
H
o
ho: ✁
The local variables hi, ho, and hiho are each shown to point to the relevant
parts of the structures. The list pointed to by the variable hi has been copied by
Revision: 6.34
188 Chapter 6. Lists
append. The other list, pointed at by ho, is shared. The function list_hi_ho
returns the result list hiho. The list pointed at by hi is by then no longer accessi-
ble, the list pointed at by ho is accessible as part of the result list. The heap storage
for the inaccessible hi list can be reclaimed. The storage occupied by the ho list
cannot be reclaimed, because it is still accessible. In general, it is difficult to de-
termine which heap structures will be redundant and which structures are still in
use. Consequently reusing heap data structures should be done with care. See also
Section 6.8.
The filter function can be written directly in C, using the techniques that have
been developed earlier in this chapter:
char_list filter( bool (*pred)( char ), char_list x_xs ) {
if ( x_xs == NULL ) {
return NULL ;
} else {
char x = head( x_xs ) ;
char_list xs = tail( x_xs ) ;
if( pred( x ) ) {
return cons( x, filter( pred, xs ) ) ;
} else {
return filter( pred, xs ) ;
}
}
}
Let us now use filter to select all digits from a list of characters. The SML ver-
sion is:
(* filter_digit : char_list -> char_list *)
fun filter_digit xs = filter digit xs ;
Revision: 6.34
6.4. Append, filter and map: recursive versions 189
Revision: 6.34
190 Chapter 6. Lists
if ( x_xs == NULL ) {
return NULL ;
} else {
char x = head( x_xs ) ;
char_list xs = tail( x_xs ) ;
if( pred( arg, x ) ) {
return cons( x, extra_filter( pred, arg, xs ) ) ;
} else {
return extra_filter( pred, arg, xs ) ;
}
}
}
The original C version of filter was not general enough. To see extra_filter
in action, consider again the problem of filtering out elements greater than the
pivot p from a list of characters.
char_list filter_greater( char p, char_list xs ) {
return extra_filter( greater, &p, xs ) ;
}
The address of the pivot p is passed as the second argument to extra_filter,
which in turn will pass it to the predicate greater. The latter dereferences its
pointer argument arg:
bool greater( void *arg, char x ) {
char * c = arg;
return x > *c ;
}
The function greater accesses the pivot value by dereferencing the pointer arg,
which points to the pivot value p.
Exercise 6.8 Prove that the SML-function map shown above satisfies the specifi-
cation given in Exercise 6.7.
Revision: 6.34
6.5. Open lists 191
Exercise 6.9 Generalise map to extra_map in the same way as filter has been
generalised to extra_filter.
This concludes the first encounter of lists in C. The primitives append, map,
extra_map, filter, and extra_filter may be inefficient still. This issue
will be revisited in the next section. All implementations are modular, so they
would be appropriate building blocks for many list processing applications in C
programs.
Revision: 6.34
192 Chapter 6. Lists
The technique available for turning a non-tail recursive function into a tail re-
cursive one reverses the sequence of events. In this case, it would mean one of two
possibilities:
✁
To traverse the list x_xs from right to left and to build the result list up from
right to left
✁
Both options go against the flow of one list in order to go with the flow of the other.
Here is the first solution:
(* copy : char_list -> char_list -> char_list *)
fun copy accu Nil
= accu
| copy accu (Cons(x,xs))
= copy (append accu (Cons(x,Nil))) xs ;
✏✒✏✒✏
✁
✂ ✂ ✝
the problem worse rather than better. Why then have we then gone through all
this trouble? The answer is that the calls to append are not really necessary. With
some programming effort, ✎
we can improve the tail recursive version of copy such
that it will only allocate Cons cells, whilst still being tail recursive. The solution
that we will obtain is efficient, as it does precisely the amount of work that one
would expect, using only a constant amount of stack space. However, there is a fly
in the ointment: the efficient solution cannot be programmed in SML. Fortunately
it can be written nicely in C.
Exercise 6.10 Write a version of copy that traverses the input list from the right,
using foldr. Then translate the result into C.
Consider the following append based C implementation of the tail recursive ver-
sion of copy:
char_list copy( char_list xs ) {
char_list accu = NULL ;
while( xs != NULL ) {
accu = append( accu, cons( head( xs ), NULL ) ) ;
xs = tail( xs ) ;
}
return accu ;
}
Revision: 6.34
6.5. Open lists 193
if( condition ) {
statement ;
while( condition ) {
while( condition ) {
statement ;
statement ;
}
}
}
The result of unrolling is that the first iteration of the loop is executed before the
while. The statements of the first iteration, now separate from the statements
of subsequent iterations, can be optimised using the knowledge that all variables
have their initial values. This information will help to create an efficient function.
Revision: 6.34
194 Chapter 6. Lists
Revision: 6.34
6.5. Open lists 195
The statement above has exactly the same meaning as the two statements below,
because the second assignment is used as an expression (explained in Chapter 3):
accu = cons( head( xs ), NULL ) ;
last = accu ;
To illustrate the working of the open list based function copy, consider the follow-
ing C function:
char_list list_ho( void ) {
char_list ho = cons( H, cons( o, NULL ) ) ;
char_list copy_ho = copy( ho ) ;
return copy_ho ;
}
The list ho is created first:
ho: H
o
✁
After creation, the list is copied to copy_ho. The first step makes a copy of the
first list_struct cell:
accu: H
✁
last:
The next step remembers, through the use of the variable last, where the previ-
ous step has put its data and attaches the copy of the next cell at this point:
accu: H
o
last: ✁
Revision: 6.34
196 Chapter 6. Lists
accu: ✁
last:
The first assignment statement of the while-statement then creates a new cons
cell, and the second assignment-statement stores this pointer via *last at the end
of the list, which is initially in accu. The third statement then updates last so
that it points to the new tail of the list, the element tail_list of the new node.
The states of accu and last are shown graphically below:
accu: H
✁
last:
The next iteration creates a new cello and uses last to place the new cell at the
end of the list. The variable last is updated to point to the end of the list again,
which results in the following state:
Revision: 6.34
6.5. Open lists 197
accu: H
o
last: ✁
This is the last iteration of the loop, so the function returns the newly created list.
This completes the description of two efficient implementations of copy based
on open lists. These two implementations are functionally identical (as they are
identical to the naive implementation that would use a call to append). The im-
plementations are also good building blocks: The internals of the two implemen-
tations may be non-functional, but the functions provide a clean interface to their
callers without side effects.
End of input: where copy returns the empty list Nil, append returns its extra
argument list ys.
Revision: 6.34
198 Chapter 6. Lists
last = last->list_tail
= cons( head(xs), ys ) ; /*replaced NULL by ys*/
xs = tail( xs ) ;
}
}
return accu ;
}
Programming with open lists can be rewarding if the functions are derived in a
systematic fashion. However, it is easy to make mistakes, so care should be taken
when using this technique.
Exercise 6.11 Write a version of append that uses the advanced pointer technique
for open lists from Section 6.5.2.
Exercise 6.12 Page 191 shows a recursive version of map. Implement a version of
map that uses open lists.
Exercise 6.13 Page 188 shows a recursive version of filter. Implement a version
of filter that uses open lists.
Revision: 6.34
6.6. Lists versus arrays 199
The store arrangement for the data structures allocated for list and array are:
list: H
i
array: H H
i o
H ✁
The array is stored as a contiguous block. Therefore the array requires less space,
because no pointers are necessary to connect the elements of the data structure, as
for lists. More importantly, it is possible to calculate offsets from the beginning of
the array and to use these offsets to access an arbitrary element directly. On the
other hand the fact that a list is not stored as a contiguous block, but as a chain of
blocks, makes it inexpensive to attach a new element to the front of the list. An
array would have to be copied in order to achieve the same effect.
In designing algorithms that operate on a sequence, the appropriate data rep-
resentation must be selected. Depending on the relative importance of the various
operations, the list or the array will be more appropriate. There are other data
structures that may be used to represent a sequence, such as streams, queues, and
trees. These will have different characteristics and may therefore be more appro-
priate to some applications. Streams are discussed in Chapter 7 and trees are the
subject of a number exercises at the end of this chapter. A discussion of the more
advanced data structures can be found in a book on algorithms and data struc-
tures, such as Sedgewick [12].
Revision: 6.34
200 Chapter 6. Lists
end ;
The lower bound of the array s is l=0, and the upper bound is u=length s - 1.
Thus, the range of possible index values is l . . . u.
The function array_to_list is inefficient for several reasons. Firstly, it cre-
ates the list of index values as an intermediate data structure, which is created and
then discarded. Secondly, it uses the non-tail recursive function map, which causes
the solution to require an amount of stack space proportional to the length of the
array.
There are alternative solutions that do not suffer from these problems. To avoid
the intermediate list, one could write a directly recursive solution. This will be
left as an exercise. We will explore a second alternative that yields the optimal
efficiency.
map ✄✂
✁ ✁ ✁ ✟✟✟
✢✟ ✂ ✢ ✢✟ ✢ ✢✟ ✢ ✢
✆
✄✂
✁ ✟✡✂ ✁✟ ✁✟ ✆
✄✂
✟✟✟
foldr ✂
✁✟ ✂ ✁✟ ✁✟ ✆
✄✂
✟✟✟
The relationship illustrated above is when given that ✟ ✂ ✄ ✟✲ ✂ ✄ then we
have:
Exercise 6.15 Prove (6.1) for finite lists by induction over the length of the list
xs.
Revision: 6.34
6.6. Lists versus arrays 201
array is . Thus, accessing the elements of the array from left to right or from
right to left is immaterial. In the next chapter, similar examples will appear that do
not have this property. In one of these examples, text will be read from a stream,
which must happen strictly from left to right. The consequences of this seemingly
innocuous property will be far reaching.
Revision: 6.34
202 Chapter 6. Lists
Revision: 6.34
6.6. Lists versus arrays 203
The explicitly recursive form of the new list_to_array allows for the mul-
tiple argument while-schema to be applied. After simplification, this yields the
following C function:
char * list_to_array( char_list xs ) {
int n = length( xs ) ;
char * array = malloc( n ) ;
int i = 0 ;
if( array == NULL ) {
printf( "list_to_array: no space\n" ) ;
abort( ) ;
}
while( xs != NULL ) {
array[i] = head( xs ) ;
i = i+1 ;
xs = tail( xs ) ;
}
return array;
}
For each iteration, both the index i range and the list of values xs are advanced.
The C function list_to_array efficiently transfers the contents of a list of
characters into an array of characters because the list is traversed from left to right.
At each step the head and the tail of a list are accessed, these are both efficient
✁ ✟
operations ( ).
The moral of the story is that it is a good idea to ‘go with the flow’. If a data
structure supports some operations more efficiently than others, one should use
the efficient ones. In the case of lists, this means a traversal from left is preferred
above a traversal from the right.
Exercise 6.17 Storing a single character in each cons cell makes the list of charac-
ters representation uneconomical in terms of its space usage. Reimplement
the string library so that it uses a list of arrays, using the following C data
structure:
typedef struct string {
char data[ 32 ] ;
int length ;
struct string *next ;
} *string ;
Revision: 6.34
204 Chapter 6. Lists
Revision: 6.34
6.7. Variable number of arguments 205
Revision: 6.34
206 Chapter 6. Lists
Revision: 6.34
6.8. Store reuse 207
Revision: 6.34
208 Chapter 6. Lists
6.9 Summary
The following C constructs were introduced:
Recursive types Structures need to contain a pointer to a value of their own type
in order to create a linked list. It is not legal to refer forward to a type in C,
so instead, the struct must be named, this name is then used in combination
with the keyword struct to refer to the type:
typedef struct {
✄
struct ✄ * ;
} ✁ ;
✎
NULL pointer The NULL pointer is a pointer that does not point to anything. It
is used to signal error conditions (functions returning NULL), and it is used
to signal the end of lists.
Memory allocation The function malloc allocates memory, like calloc, but
memory is not initialised, and only one cell of the specified size is allocated.
The function free releases memory.
Revision: 6.34
6.9. Summary 209
It is important to choose the right data structure. The main advantage of the
list over the array is that lists are more easily extended and contracted. The
main disadvantage of the list is that the cost of accessing an element is not
uniform.
✁
The C programmer must carefully consider the issue of allocating, reuse, and
deallocating the storage used for list cells. Premature deallocation or reuse
causes programs to go disastrously wrong. Late deallocation or reuse may
cause programs to exceed the available space and crash.
✁
The C programmer has to take care that operations on lists do not inadver-
tently walk beyond the end of the list. It is wise to build checks into a pro-
gram, so that if this happens, a sensible warning is produced. Functional
programs are checked automatically.
✁
The list processing functions can be implemented efficiently by using tail re-
cursion with an accumulating argument. The open list technique was intro-
duced for accumulating a list. We have seen two implementations of this
technique:
– The first implementation maintains a pointer to the cell that has been
allocated last. The next cell can then be linked via the appropriate field
of the last cell at constant cost.
Revision: 6.34
210 Chapter 6. Lists
(b) Write a C function mkLeaf to create a leaf node of the tree type. The
function mkLeaf should have the following prototype:
tree_ptr mkLeaf( int leaf ) ;
Also write a function mkBranch with the prototype below to create an
interior node.
tree_ptr mkBranch( tree_ptr left, tree_ptr right ) ;
For each branch, print its left branch and print its right branch,
separated by a comma and enclosed in parentheses. As an exam-
ple, the tree below should be printed as (1,(2,3)).
2 3
Revision: 6.34
6.10. Further exercises 211
(d) A binary tree can be rotated by swapping the left and right fields at
every interior node. For example:
rotate (Branch(Leaf(1),Leaf(2)))
= Branch(Leaf(2),Leaf(1)) ;
rotate (Leaf(0))
= Leaf(0) ;
First write a function rotate in SML. Then write a C function with
exactly the same functionality as the SML function, that is, make sure
that the original tree is not destroyed.
(b) Write two functions mkBind and mkData to allocate the appropriate
version of the tree_struct on the heap. Use the vararg facility so
Revision: 6.34
212 Chapter 6. Lists
that the function takes an arbitrary number (possibly zero) of sub trees,
and calloc to allocate sufficient memory to hold the pointers to the
sub trees. The required prototypes of the functions are shown below.
tree_ptr mkBind( tree_ptr * b ) ;
tree_ptr mkData( char k, int s, ... ) ;
Both have tag Data and if both have the same key and size. In
addition, all subtrees must match.
✁
If the argument pat has tag Bind. In that case, the field bind
should be set to remember the value of exp. You may assume that
there are no nodes with tag Bind in the trees offered as the second
argument to match.
(d) Write a main function to create some sample patterns and expressions
to test your match function.
Revision: 6.34
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 7
Streams
The previous chapters discussed two data structures for storing sequences of data
items: lists and arrays. A third kind of sequence is the stream. An example of a
stream is the sequence of characters being typed by the user as input to a program,
or conversely, the characters being printed on the screen.
In this chapter, streams are discussed, and they are compared with the two
other sequences. A stream has two distinguishing features:
✁
A stream can only be accessed sequentially. That is, after accessing element
✟ of the stream, only the next element
✟
✁ ✂ ✟
✟
In C and SML streams work by side effects. That is, reading a character from
a stream uses a function that returns a character (the result), and updates
some state to record that this character has been read. Writing a character
deposits the character on the stream, and updates some state to record this.
213
214 Chapter 7. Streams
✄ ✄ ✄ ✄ ✏
As a first solution, we can use two functions. One function captures the stream in
a list, and another function counts the number of full stops in a list. For the sec-
ond we use the SML function full_stop_count shown below. Please note that,
whilst we used a monomorphic type char_list in Chapter 6, here we use the
standard SML type list with the usual polymorphic operators, such as length
and filter.
(* full_stop_count : char list -> int *)
fun full_stop_count cs
= let
fun is_full_stop c = c = "."
in
length (filter is_full_stop cs)
end ;
The second aspect of the solution is to capture the input stream. In SML streams
are accessed using two functions. The predicate end_of_stream(stream) de-
termines whether the end of a stream has been reached. For as long as the predi-
cate yields false (and there are thus elements to be accessed from the stream), the
function input(stream,n) can be used to access the actual characters from the
stream. The argument stream identifies the stream. The argument n specifies
how many characters should be accessed. The function input has a side effect
on the stream, in that it makes sure that a subsequent call to input will cause the
next character(s) to be accessed. A complete SML function to transfer all charac-
Revision: 6.33
7.1. Counting sentences: stream basics 215
Revision: 6.33
216 Chapter 7. Streams
A function stream_to_list that uses getc to create a list from the standard
input can now be implemented in C:
char_list stream_to_list( FILE * stream ) {
int c = getc( stream ) ;
if( c == EOF ) {
return NULL ;
} else {
return cons( c, stream_to_list( stream ) ) ;
}
}
The function stream_to_list processes one character at a time. This charac-
ter is then stored as the head of the result list using the cons function. The
rest of the input is read by a recursive invocation of stream_to_list. The
stream_to_list function returns an empty list using the NULL pointer when
it encounters the end of the input.
We can now give a naive, but elegant, C function sentence_count to count
the number of full stops in a text:
int sentence_count( FILE * stream ) {
return full_stop_count( stream_to_list( stream ) ) ;
}
The given C solution to the sentence count problem would work, but only for
small streams. The solution is inefficient because it requires an amount of store
proportional to the length of the stream. There are two reasons why this is the
case. Firstly, the function stream_to_list uses an amount of stack space pro-
portional to the length of the stream. Secondly, the entire stream is transferred
into the store during the computation. We will solve both efficiency problems us-
ing the techniques that were developed in Chapters 3 and 6.
Revision: 6.33
7.1. Counting sentences: stream basics 217
The two functions have the same structure. The main difference is that the list xs
plays the same role in copy as the stream does in stream_to_list. In detail,
the differences are:
The arguments. The function copy has an explicit argument representing the list
being accessed. The function stream_to_list relies on the SML primitive
input to remember at which point the current file is being read.
The termination condition. The test of ‘end of input’ for copy checks whether
the end of the list has been reached using xs = []. The corresponding test
end_of_stream stream in stream_to_list checks whether the end of
the stream has been reached.
The next element. While the end of the input has not been reached, copy
constructs a new list element using the next element of the input list
xs while stream_to_list uses the character it has just accessed using
input(stream,1).
Side-effects. The copy function is pure, that is, it has no side effect. The
stream_to_list function is impure, since it has a side effect on the stream.
Making changes to the open list version of copy that reflect the above differences
yields the following efficient C version of stream_to_list. The changes have
been annotated in the comments:
char_list stream_to_list( FILE * stream ) { /* name */
char_list accu = NULL ;
char_list last ;
int c; /* c declaration */
if( (c = getc(stream) ) != EOF ) { /* test on end */
last = accu
= cons( c, NULL ) ; /* c not head.. */
/* no statement here because getc side effects */
while( (c = getc(stream) ) != EOF ) { /* test on end */
last = last->list_tail
= cons( c, NULL ) ; /* c not head.. */
/* no statement here because getc side effects */
}
}
return accu ;
}
Please note the use of assignments in expressions here to save the character just
read for later use. This is idiomatic C and also quite clear.
Exercise 7.1 Rewrite stream_to_list without assignments in expressions and
comment on the resulting duplication of code.
Exercise 7.2 Another implementation of stream_to_list would use pointers
to pointers, as in the second form of copy and append. Implement
stream_to_list using pointers to pointers.
Revision: 6.33
218 Chapter 7. Streams
The function stream_to_list efficiently transfers text from a stream into a list.
This avoids the problem of the unbounded amount of stack space in our initial
solution to the sentence count problem.
count is tail-recursive.
✁
Revision: 6.33
7.1. Counting sentences: stream basics 219
}
}
}
Removing the need for the intermediate list is not only more efficient in space, but
it also makes the program run faster.
Input
There are three more functions to read from an input stream: getchar, scanf,
and fscanf. The function getchar(), reads a character from the standard input
stream, it is the same as getc( stdin ). The function scanf reads a number of
data items of various types from the standard input stream and stores the values
of these data items (scanf stands for ‘scan formatted’). The function scanf can
be used to read strings, integers, floating point numbers, and characters. The first
argument of scanf specifies what needs to be read; all other arguments specify
where the data should be stored. As an example of scanf, consider the following
call:
int i, n ;
char c ;
double d ;
n = scanf("%d %c %lf", &i, &c, &d ) ;
This section of code will read three data items: an integer (because of the %d in the
format string), which is stored in i; a character (denoted by the %c in the format
Revision: 6.33
220 Chapter 7. Streams
string), which will be stored in the variable c; and a floating point number, which
will be stored in d, indicated by the %lf (more about this %-sequence later on). The
return value of scanf is the number of items that were successfully read. Suppose
the input stream contains the following characters:
123 q 3.14
The variable i will receive the value 123, c will receive the valueq , and d will
receive the value 3.14. The function call returns 3 to the variable n because all
items were read correctly. Suppose the stream had contained instead:
123/*3.14
Now the scanf would have read the 123 into i, read/ into c, and returned
2, as the asterisk is not (part of) a valid floating point number. When scanf fails,
the next use of the stream will start at the first character that did not match, the
asterisk in this example.
Any character in the format string of scanf that does not belong to a %-sequence
must be matched exactly to characters in the input stream. The exception to this
rule is that spaces in the format string match any sequence of white space on the
input stream. Consider the following call as an example:
int i, n ;
char c ;
n = scanf( "Monkey: %d %c", &i, &c ) ;
This will give the value 2 to the variable n if the input is:
Monkey: 13 q
It will return 0 to n if the input is:
Money: 100000
In the latter case, the characters up to Mon will have been read, the next character
on the stream will be the e from Money.
The function scanf returns any number of results to its caller through its
pointer arguments. It is convenient to do so in C, but, as we have already seen,
it is a type-unsafe mechanism: the compiler cannot verify that the pointers passed
to scanf are pointers of the right type. The following call will be perfectly in or-
der according to the C compiler:
int i, n ;
char c ;
double d ;
n = scanf("%lf %d %c", &i, &c, &d ) ;
However, scanf will attempt to store a floating point number in the integer vari-
able i, an integer in the character variable c, and a character in the double variable
d. Therefore, this program will execute with undefined results.
The way floating point numbers are accessed is a source of problems. A %f-
sequence reads a single precision floating point number, while %lf reads a double
precision floating point number. Until now we have ignored the presence of single
precision floating point numbers of type float, but scanf must know the differ-
ence. Writing a single precision floating point into the store of a double precision
Revision: 6.33
7.1. Counting sentences: stream basics 221
number does not give the desired result. Another common error using scanf is to
forget the & in front of the arguments after the format string, causing the value to
be passed instead of a pointer, most likely resulting in the function scanf crash-
ing.
The function scanf always accesses the standard input stream stdin.
The function fscanf has an extra argument that specifies the stream to be
accessed. Similar to getchar and getc, scanf( ... ) is identical to
fscanf( stdin, ... ). An example of its use is given after the description
of the output functions.
Output
Similar to the family of input functions, getchar, getc, scanf, and fscanf,
there is a family of output functions: putchar, putc, printf and fprintf.
The companion of getchar to put characters on the output stream stdout
is called putchar. Here is a sequence of six statements that is equivalent to
printf( "Hello\n" ):
putchar( H ) ; putchar( e ) ; putchar( l ) ;
putchar( l ) ; putchar( o ) ; putchar( \n ) ;
The function call putchar( c ) is equivalent to a call to printf( "%c", c ).
The function putc( c, stream ) allows a character c to be output to the
named stream. Thus putchar( c ) is equivalent to putc( c, stdout ). Fi-
nally, the function fprintf has an extra argument that specifies to which stream
to write: printf( ... ) is identical to fprintf( stdout, ... ).
An Input/Output Example
The following program fragment reads an integer and a double precision floating
point number from the file “input.me” and writes the sum to the file “result”:
int i, n ;
double d ;
FILE *in = fopen( "input.me", "r" ) ;
if( in != NULL ) {
FILE *out = fopen( "result", "w" ) ;
if( out != NULL ) {
n = fscanf( in, "%d %lf", &i, &d ) ;
if( n == 2 ) {
fprintf( out, "%f\n", d+i ) ;
} else {
fprintf( stderr, "Wrong format in input\n" ) ;
}
fclose( out ) ;
}
fclose( in ) ;
}
Revision: 6.33
222 Chapter 7. Streams
✁ ✟ ✁ ✟ ✁ ✟
mean sentence length div sentence count (7.2)
✟
✄ ✄ ✄
Revision: 6.33
7.2. Mean sentence length: how to avoid state 223
are evaluated, either character_count accesses the entire stream, so that when
sentence_count is evaluated, it finds the stream empty, or vice versa. This
shows the danger of using functions that have side effect: they make it difficult
to create good building blocks. It depends on the circumstances whether a func-
tion that relies on side effects has the desired behaviour or not. The C versions
of sentence_count and character_count also modify the stream as a side
effect, so there is no point in trying to pursue development on the present basis.
The mean sentence length problem could be solved by transferring the entire
stream into a list and then to count the characters and the sentences. Such a so-
lution was dismissed earlier because of the inherent inefficiency and also the im-
possibility of processing arbitrary large streams. A better solution is to combine
the activities of sentence_count and character_count into a single function
that accesses the elements of the stream just once.
Such a combined function can be developed, since the structures of
sentence_count and character_count are so similar. The SML functions can
be merged, whilst taking care not to duplicate the elements they have in common:
(* mean_sentence_length : instream -> int *)
fun mean_sentence_length stream
= let
fun count chars stops
= if end_of_stream stream
then chars div stops
else if input (stream, 1) = "."
then count (chars+1) (stops+1)
else count (chars+1) stops
in
count 0 0
end ;
The auxiliary function count inspects each character of the input stream in turn
and counts the number of full stops and characters in parallel. This means that
the input stream does not have to be remembered, and the implementation can
discard each character of the stream after it has been inspected.
The version of mean_sentence_length above can be transformed into an
efficient C function without difficulty:
int mean_sentence_length( FILE * stream ) {
int chars = 0 ;
int stops = 0 ;
int c ;
while( true ) {
c = getc( stream ) ;
if( c == EOF ) {
return chars / stops ;
} else if( c == . ) {
stops++ ;
}
Revision: 6.33
224 Chapter 7. Streams
chars++ ;
}
}
This is an efficient function to calculate the mean sentence length. Many functions
that consume a stream can be written using a while loop that iterates over the char-
acters of the input stream, accumulating all the required information.
below, where both the word and the text are represented by a sequence of charac-
ters:
✁✄✂ ☎ ✡ ✆✠✂ ☎✲✡ ✟☛✡ ✂☎
word count ✁ ✁
✁ ✍ ✟ ✁ ✍ ✟ ✢
word count ✁ ✁ ✂ ✁✁ ✁ ✁✄✂ ✁✁ ✯ (7.3)
✟
✞ ✗ ✄
✞
✄
An algorithm to count all the occurrences of the word w would try to match it to
each position in the text t. Using lists to represent both the word and the text,
successful matches can be counted as follows in SML:
(* word_count : char list -> char list -> int *)
fun word_count ws [] = 0
| word_count ws (t::ts) = if match ws (t::ts)
then 1 + word_count ws ts
else word_count ws ts ;
The word_count function requires a subsidiary function match to check whether
a word matches the text at a particular position. The match function compares the
characters from the word and the text. It yields false if either the text contains too
few symbols or if a mismatch is found:
(* match : char list -> char list -> bool *)
fun match [] t = true
| match (w::ws) [] = false
| match (w::ws) (t::ts) = w = (t:char) andalso match ws ts ;
✁ ✍ ✟
Exercise 7.5 Given a word , a text ✁ , and (7.3). Prove that: word count
✞ ✞ ✁
word_count w t
As a first step, we use the naive approach: transfer the contents of a stream into a
list and then count the number of occurrences of the word “cucumber”:
(* main : instream -> int *)
Revision: 6.33
7.3. Counting words: how to limit the size of the state 225
Exercise 7.6 Rewrite word_count above as a tail recursive function using an ac-
cumulating argument and translate the result into a loop based C imple-
mentation.
t: c
w: c u c u m b e r
The word and the text are shown with corresponding matching characters verti-
cally aligned. Now the second character of the text must be accessed for it to be
compared to the second character of the word. The characteru enters the queue
at the rear (right).
Revision: 6.33
226 Chapter 7. Streams
t: c u
w: c u c u m b e r
After accessing another three characters a mismatch occurs, because the text reads
“cucuc. . . ” and the word reads “cucum. . . ”. This causes the match function to
return false to word_count. The queue of elements from the text is now in the
following state:
t: c u c u c
w: c u c u m b e r
The function count makes the next step; it advances the position of the queue in
an attempt to match “u. . . ” with “c. . . ”. The characterc at the front of the queue
has now left.
t: u c u c
w: c u c u m b e r
The match now fails immediately. The queue is advanced yet again, so that now
also the characteru at the front of the queue disappears.
t: c u c
w: c u c u m b e r
Now we see the benefits of using a queue sliding over the text. The matching of
the charactersc ,u andc can be done immediately; there is no need to
access new elements from the stream because they have been remembered.
Revision: 6.33
7.3. Counting words: how to limit the size of the state 227
It should be possible to discard elements from the front of the queue as soon
as they are no longer needed.
The sliding queue can be implemented on the basis of a list. We will first study
this in SML and then give an optimised implementation in C. The SML datatype
queue that we use for the queue is a tuple consisting of an input stream and a list:
datatype a queue = Queue of (instream * a list) ;
The actual elements that are part of the queue are stored in the list. We are aiming
to always keep a certain number of elements in the list. In the context of the word
count problem, we know in advance that we will be needing as many elements
as there are elements in the word that we are counting. To support the transfer of
elements from the text, we need to remember with which stream we are dealing.
Initially, our program will have to create a queue with a number of characters
from the stream entered into the queue. Here is an SML function to do this:
(* create : instream -> int -> char queue *)
fun create stream n
= Queue (stream, stream_to_list stream n) ;
The function create tries to access n characters from the stream and stores these
in the list. We are using here a modified version of stream_to_list which reads
no more than n characters. The identity of the stream must be remembered for
future use.
To fetch the contents of the queue, we define the SML function fetch:
(* fetch : a queue -> a list *)
fun fetch (Queue (stream, list)) = list ;
When advancing the queue we must make sure that a new character is automat-
ically entered when an old character is removed. New characters must be trans-
ferred from the stream involved. Here is the SML function advance taking care
of this:
(* advance : char queue -> char queue *)
fun advance (Queue (stream, list))
= if end_of_stream stream
then Queue (stream, tail list)
else Queue (stream, tail list @
[input (stream, 1)] ) ;
Appending a newly accessed stream element to the rear of the queue is relatively
expensive as it requires the use of the standard SML append operator @. In the
optimised C version of this solution, we will use the open list technique rather
than an append operation.
Revision: 6.33
228 Chapter 7. Streams
Revision: 6.33
7.3. Counting words: how to limit the size of the state 229
Revision: 6.33
230 Chapter 7. Streams
q:
q->queue_stream: (5)
new (1)
(2)
q->queue_first: c u m b
(5)
old (4,6)
q->queue_last:
(3)
queue
Revision: 6.33
7.3. Counting words: how to limit the size of the state 231
}
return accu ;
}
Exercise 7.10 Implement a main program to initialise the queue and count the
number of occurrences of some word in a text.
Revision: 6.33
232 Chapter 7. Streams
Exercise 7.11 We might need an extra function valid to tell us how many valid
elements there are in the queue. Give these functions in C and SML.
Advancing the queue with an array based implementation is now slightly compli-
cated, as we will need to shift the contents of the array. Here is the SML version of
advance:
(* advance : char queue -> char queue *)
fun advance (Queue (stream, valid, array))
= let
fun shift i = if i < valid
Revision: 6.33
7.3. Counting words: how to limit the size of the state 233
Exercise 7.12 Rewrite the SML and C versions of word_count and match so that
they become suitable for array based queues.
Exercise 7.13 The functions above shift all the elements in the array one posi-
tion to the left on every advance. This operation is increasingly expensive
for long words. An alternative is to use a cyclic buffer, where the text is
Revision: 6.33
234 Chapter 7. Streams
✪
✁ ✟
✂
a cyclic buffer.
7.4 Quicksort
The final example of this chapter discusses the problem of sorting the contents of a
stream. The problem here is that it is impossible to sort a stream efficiently without
having access to its entire contents. This time, no optimisations apply that only
keep part of the stream in the store. The problem of efficient sorting is by itself
interesting and worthy of study.
To sort data, it should be possible to compare two arbitrary data elements and
to decide which of the ✎two should come first in a sorted sequence. When given
a sequence of length
✄ and an
✁ ✟
operation defined on the elements of the
✂ ☛
sort
✁ ✟
✄ ✗✚✪ ✄✡ ✄
✁
✂✁
✟✏✍
✍
✏✒✏✒✏
✎ ✡
✄
✁
☎ ✯
✟
✍ ✎
where ✗ ✂✁ ✏✒✏✒✏ ☎ ✯ is a permutation of ✗✚✪ ✏✒✏✕✏ ✯
✁ ✟ ✁ ✟ ✁ ✟
and ✄
✁ ☛ ✄
✂ ☛ ✏✕✏✒✏ ✄
☎
An elegant divide and conquer algorithm to sort a sequence is quicksort [3]. The
recursive specification of quicksort on sequences is given below. Here we are us-
ing the filter function
✁
as ✟ defined in Exercise 6.6. The predicates are written as sec-
tions; for example ☞ , when applied to some value ✟ , is the same as ✟
✄
✡ ☞ . ✡
✁ ✟
☞ ✛ range ✏
✝✞
✁ ✁ ✍ ✟ ✟
✞
☞
✞
✁ ✟ ✟
qsort filter ✁ ✄
✍
✄
✟
qsort filter ✁ ☞
✟
✄
✁ ✱ ✍ ✟ ✟✏✍ ✱
✄ ✄
✄ ✞ ✄
qsort filter ☞ if
✞
✪
✞
✍
✞✠
✄ ✄
✄ otherwise
Some element ☞ of the sequence is used as a ‘pivot’. The three filters partition the
elements less than the pivot, those equal to the pivot (of which there is at least
one), and the elements which are greater than the pivot into three separate sets.
The elements of the first and third partitions are sorted recursively. The two sorted
partitions are finally joined with the pivot partition to form the result.
Quicksort can be implemented using both lists and arrays. The list based im-
plementation is elegant, but not efficient. The array based implementation is com-
plicated, but more efficient. Both implementations will be discussed in the follow-
ing sections.
Revision: 6.33
7.4. Quicksort 235
Exercise 7.14 Amend the code for quicksort so that redundant lists are deallo-
cated. Assume that append( x, y ) copies the list x, cons creates one
Revision: 6.33
236 Chapter 7. Streams
Exercise 7.15 Write a function main in both SML and in C to call qsort with the
following short list of characters:
✍ ✍ ✁
✍ ✍ ✍ ✍
☎
The list version of quicksort is elegant and compact, and it was not particularly
difficult to derive. Unfortunately, it is not efficient because of the large number of
intermediate lists it creates. There are two sources of inefficiency. Firstly, for each
invocation of qsort, the input list is traversed twice, creating two separate parti-
tions in the form of separate lists. Each element (except the first) of the input list
will end up in one of the two partitions. Thus each call to qsort effectively copies
its entire input list during the partitioning phase. Secondly, the calls to append
create copies of the lists as produced by the recursive calls to qsort.
In the next section we turn to the array version of qsort with a view of avoid-
ing all copies of intermediate data structures. The price that has to be paid for this
improved efficiency is considerable complication in partitioning the input data.
An unexpected advantage is that the append operation becomes redundant when
using an array.
Revision: 6.33
7.4. Quicksort 237
efficient C version will access the old values at i and j and update them as fol-
lows:
void swap( char data[], int i, int j ) {
char data_i = data[i] ;
char data_j = data[j] ;
data[i] = data_j ;
data[j] = data_i ;
}
The function swap has a side effect on the array data. The void return type ad-
vertises that swap does its work by a side effect.
Let us now consider how an array based version of qsort could be developed.
Firstly, the input data is needed in the form of an array. Secondly, the array must
be partitioned such that one partition contains the array elements not exceeding
the pivot and another partition contains the elements greater than the pivot. Ele-
ments equal to the pivot will occupy a partition of their own. Thus, an important
element of the solution is to maintain various indices in the array, in order to keep
tabs on the partitions involved.
Each call to qsort works on a section of the array, delineated by two indices
l and r (for left and right). The function qsort will leave all other array elements
undisturbed. The partitions within the range l to r will be further delineated us-
ing index variables j and i. The elements less than or equal to the pivot are at
positions l to j; those greater than or equal to the pivot are at positions i to r.
The intervening elements (from j+1 to i-1) are equal to the pivot and require no
further sorting. Here is a diagram showing the partitions and the role of variables
delineating the partitions:
Revision: 6.33
238 Chapter 7. Streams
The partition function receives the index position p of the pivot, the pivot itself
data_p, the input array data, the boundaries l and r of the section of the array to
be partitioned, and the initial values of i and j. The partition function returns
the data array with the elements between l and r partitioned, and it also returns
the boundaries of the new partitions as i and j . The array data is passed to
a recursive qsort to order the elements between l and j . The resulting array
data is passed to the second recursive invocation of qsort. The latter orders
the elements between positions i and r and returns this result as data .
The C solution follows the functional solution closely, except where the call to
partition is concerned. Here the functional version returns, along with the new
array data , two indices i and j . A C function can be made to return a data
structure containing these elements, but it is not idiomatic C to do so. Instead, one
would pass the address of the variables i and j to the partition function so that it
may update these. The operator & is used to deliver the address of the variables.
The partition function already has a side effect (it updates the data array), so
nothing is lost by making it update the variables i and j as well.
void qsort( char data [], int l, int r ) {
if( l < r) {
int p = l ;
char data_p = data[p] ;
int i = l ;
int j = r ;
partition( p, data_p, data, l, &i, &j, r ) ;
qsort( data, l, j ) ;
qsort( data, i, r ) ;
}
}
The idea of an efficient partitioning is to move the elements of the array by ex-
changing two elements at a time. This consists of a number of steps:
up The up sweep starts at the left most position (initially l) and compares the el-
ements it finds with the pivot. It keeps moving to the right until an element
is found that is greater than the pivot.
down The down sweep starts at the right most element of the partition (initially
r) and keeps moving to the left until an element is found that is less than the
pivot.
swap While the up and down sweeps have not met, swap the last elements en-
countered by both up and down, as they are both in the wrong partition.
Restart the up sweep and down sweep from the next positions.
stop The process stops as soon as the up and down phases meet.
The figure below illustrates the partitioning process on a sample array of 7 ele-
ments (indexed by 0 to 6).
Revision: 6.33
7.4. Quicksort 239
7. ‘E’ ‘C’ ‘C’ ‘B’ ‘A’ ‘F’ ‘G’ function partition at i < j
i j the ‘F’ and the ‘C’ are swapped
8. ‘E’ ‘C’ ‘C’ ‘B’ ‘A’ ‘F’ ‘G’ function partition
i j i moves right, j moves left
9. ‘E’ ‘C’ ‘C’ ‘B’ ‘A’ ‘F’ ‘G’ function up
i,j ‘B’ ‘E’ so i moves right
☛
13. ‘A’ ‘C’ ‘C’ ‘B’ ‘E’ ‘F’ ‘G’ function partition at p < j
j i exchange ‘E’ (pivot) and ‘A’
14. ‘A’ ‘C’ ‘C’ ‘B’ ‘E’ ‘F’ ‘G’ function partition
j i move j one step further and stop
Revision: 6.33
240 Chapter 7. Streams
Revision: 6.33
7.4. Quicksort 241
until it encounters either the boundary l or an element that is less than the pivot:
(* down : char -> char array -> int -> int -> int *)
fun down data_p data l j
= if l < j andalso sub (data, j) >= (data_p:char)
then down data_p data l (j-1)
else j ;
The up and down functions can be translated into pure C functions using the while
schema:
int up( char data_p, char data[], int i, int r ) {
while( i < r && data[i] <= data_p ) {
i++ ;
}
return i ;
}
Revision: 6.33
242 Chapter 7. Streams
int r = 6 ;
char data[] = "ECFBACG" ;
qsort( data, l, r ) ;
printf( "%s\n", data ) ;
return 0 ;
}
Exercise 7.16 The partition function is used only once in qsort. Substitute the
body of the C version of partition in that of qsort and show that this
removes the need for manipulating the address of the variables i and j.
We have now two versions of quicksort: one to sort a list of characters and
one to sort an array of characters. Using the functions stream_to_list and
stream_to_array, it is easy to build a function that sorts the contents of a
stream. Such a function would not be useful, as one often is not interested in just
the sorted characters of a stream. It would be more interesting to sort the lines or
numbers contained in a stream. It is not difficult to extend the quicksort versions
for such purposes.
Exercise 7.17 Write a program that reads a file, and sorts it on a line by line basis.
7.5 Summary
The following C constructs were introduced:
Streams The type to denote a stream in C is FILE *. The stream is stored exter-
nally, it can be input by the user, from another program, or from a file. A
stream is a linear sequence, like an array and a list, but it can usually only be
accessed sequentially.
The main programming principles that we have seen in this chapter are:
✁
The re-use of volatile information, such as that accessed from a stream, re-
quires careful planning. Often, algorithms require the information from a
stream to be accessed a number of times. This is inefficient, and it pays off to
spend effort in trying to devise equivalent algorithms that glean the relevant
information from a stream whilst making a single pass.
✁
The primitives provided with the standard data types are often of a low
level of abstraction. In this chapter, we have built functions that provide a
buffered view of a stream. It is often a good idea to invest in a collection
of building blocks (functions and data structures) that deliver additional ser-
vices, in this case buffering. In a sense, the buffering facilities mitigate one
of the restrictions that streams impose, the sequential access. Within certain
limits, the elements of a stream can be accessed in an order that is not quite
sequential. Enlarging the size of the buffer weakens the limited access to the
Revision: 6.33
7.6. Further exercises 243
point where the buffer is capable of containing the entire stream. In that case
the sequential access restriction on the stream is completely hidden. The
price to pay for this flexibility is an amount of store capable of holding the
entire contents of the stream.
✁
Side effects can be useful and allow for highly efficient programs to be writ-
ten. However, a function that performs a side effect should advertise this,
because care must be taken when such a function is used as a building block.
Hidden side effects can lead to obscure errors.
✁
The open list technique has been used to develop an efficient function for
reading the contents of a stream into a list. It has also been used in our
implementation of the buffering technique. Here a separate data structure
maintains the pointers necessary to implement the open list technique.
✁
If two functions with a similar structure operate one after the other on the
same data structure, then these functions can often be merged. There is an
important tradeoff here: the two individual functions are less specialised
and therefore better building blocks than the merged result. However, the
merged function is often more efficient, because the data structure (for ex-
ample a stream) does not need to be stored.
✁
The quicksort algorithm has been presented in two forms. The first version
is elegant but inefficient and uses lists. The inefficiency is caused entirely
by the profligate use of store. To counter this misdemeanour an array based
version was developed. This version does not resemble the elegant list based
version in any way. The understanding obtained from the list based version
was used to good effect in the creation of the array based version. The effi-
ciency of the array based version is entirely attributable to the use and re-use
of the original array containing the data.
Exercise 7.19 Quicksort is not the only sorting method. Another method is bubble
sort. It compares every element in an array of data to every other element.
✎
If two elements are out of order, then they are swapped. If there are data
Revision: 6.33
244 Chapter 7. Streams
☎ ☎
✏✒✏✕✏
✎ ✎
parisons. Quicksort only makes this many comparisons in the worst case,
it makes fewer comparisons on average. Write a C function bubble_sort
and test it by sorting the list of strings supplied through argc and argv.
Exercise 7.20 Modify one of the sorting functions from this chapter to sort strings
rather than characters. Then use your sort function to sort the lines of a file,
representing the contents of a line as a string.
Exercise 7.21 Sensitive information is often encrypted such that it is difficult for
unauthorised persons to intercept that information. An ancient method for
encryption is the Caesar cipher. If a letter in the original message or plaintext
✎ ✎
is the -th letter of the character set then replace this letter by the -th ✂
letter in the encrypted message or cipher text. The key to encrypting and
decrypting the message is then the number . For example if the plain text
is “Functional C” and the key is 4, then the cipher text is “Jyrgxmsrep$G”,
for the fourth letter after F is J, and so on. Write a program to implement
the Caesar cipher method, taking the text to be processed from stdin and
writing the output to stdout. The program should take a single argument:
✂ for encryption or for decryption. Would you be able to prove for all
values of that decryption after encryption is equivalent to do nothing at
all, or, for people familiar with UNIX terminology:
(a.out +k | a.out -k) = cat
Exercise 7.22 The encryption of Exercise 7.21 is weak. If you know that the plain
text was written in, say, the English language, then you would expect the
most frequently occurring letter in the cipher text to represent the letter ‘e’,
since this is the most frequently occurring letter in English text. It should
thus not be difficult to guess what the encryption key would be. Your
guess could be confirmed by also looking at the next most frequently oc-
curring letter and so on, to see if they all agree on the key value. Frequency
tables for many natural languages are widely available. Try to get hold of
such a table for your native language and write a program to analyse a ci-
pher text produced by your Caesar cipher program so as to discover the key
with as much certainty as possible.
Revision: 6.33
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 8
Modules
All programs developed so far were small. They typically consisted of no more
than 10 functions. Real programs are larger (thousands of functions). Large pro-
grams must be designed in such a way that the code can easily be inspected, main-
tained, and reused. This process of organising is generally known as modularisa-
tion.
Modules are parts of the program that perform some specific function together.
During the design of a program, the solution is split into modules. These modules
use each other according to some well defined interface. Once the interface and
the functionality of a module are defined, modules can be inspected, designed,
compiled, tested, debugged, and maintained separately. Additionally, modules
can be reused in other programs where a similar functionality is required.
SML has a sophisticated module mechanism. It is completely integrated with
the language. An SML structure is a collection of types and functions; a signature
describes the interface of a structure, and a functor operates on structures to cre-
ate a new structure. SML modules support everything mentioned above, with the
flexibility of a polymorphic type system.
The module mechanism of C is rather different. It is probably the most crude
module mechanism of any programming language. The C module mechanism
is implemented by a separate program, the C preprocessor. The C preprocessor
takes a C program and prepares it for the C compiler. This preprocessor has no
knowledge of the syntax of C, but handles the program as text instead (indeed,
the C preprocessor can be used for many other purposes). The C module mecha-
nism consequently lacks the sophistication of a real module system, as provided
by SML.
The first section of this chapter describes the basic structure of the C module
system, and henceforth, of the C preprocessor. After that, we discuss the con-
cept of global variables as an important aspect of modularisation. Global variables
can be used to store state information that remains accessible across function calls.
When used correctly, global variables can be an asset. However, it is easy to abuse
them and obscure code. We show how global state can be stored differently, lead-
ing to a cleaner interface. This is akin to an object oriented style of programming.
After this, we show how modules can also be generalised, by discussing the coun-
terpart of polymorphism.
245
246 Chapter 8. Modules
✄ ✂ ✄
Here is the imaginary part of the complex number , , and is the real ✄ ✄
part of . Complex arithmetic works just like ordinary arithmetic, except that we
✄
✍
✄
✄ ✙
✄ ✂ ✄
✙ ✁
✁ ✟ ✁ ✟ ✁ ✟ ✁ ✟
✂
✙ ✁ ✁ ✁ (8.1)
✁ ✟✏✁ ✟ ✁ ✟ ✁ ✟
✄ ✂ ✄ ✂ ✂ ✄ ✂ ✄
✄ ✙ ✂ ✄ ✁ ✂ ✁ ✂ ✂ ✒✁
✄ ✂ ✄ ✂ ✄ ✁ ✂ ✄ ✁ (8.2)
✁ ✟
Equality (8.2) is true because .
The implementation of complex subtraction and multiplication as functions in
SML is given below. The complex_distance function gives the distance from
the origin of the complex plane.
structure Complex = struct
type complex = real * real ;
Revision: 6.38
8.1. Modules in C: files and the C preprocessor 247
typedef struct {
double im , re ;
} complex ;
Revision: 6.38
248 Chapter 8. Modules
Revision: 6.38
8.1. Modules in C: files and the C preprocessor 249
Revision: 6.38
250 Chapter 8. Modules
Revision: 6.38
8.1. Modules in C: files and the C preprocessor 251
complex.c, these checks are not performed. Again, the compiler will usually find
some other error, for example, because the type complex has not been defined.
Modules that do not define types may compile without problem though. Most
modern compilers will warn the programmer on request when a function is used
that was not prototyped. This warning catches 99% of the errors that arise because
of improper use of the module facility.
Both problems are due to the looseness of the module system of C. Languages
like Modula-2 and SML do not suffer from these problems, since their module sys-
tems were designed to be watertight.
typedef struct {
vector *columns ;
int coordinates ;
} matrix ;
extern matrix matrix_multiply( matrix x, matrix y ) ;
extern vector matrix_vector( matrix x, vector y ) ;
Finally, the vector header file defines some type for a vector:
typedef struct {
double *elements ;
int coordinates ;
} vector ;
extern double vector_multiply( vector x, vector y ) ;
extern vector vector_add( vector x, vector y ) ;
When the interface of the graphics module is used somewhere, the C preproces-
sor will expand all include directives to import all types and primitives. This will
result in the following collection of C declarations:
typedef struct { /*Lines imported by graphics.h*/
Revision: 6.38
252 Chapter 8. Modules
double *elements ;
int coordinates ;
} vector ;
extern double vector_multiply( vector x, vector y ) ;
extern vector vector_add( vector x, vector y ) ;
typedef struct {
double *elements ;
int coordinates ;
} vector ;
#endif /* VECTOR_H */
This header file should be read as follows: the part of text enclosed by
the #ifndef VECTOR_H and #endif will only be compiled if the identifier
VECTOR_H is not defined (#ifndef stands for ‘if not defined’). Thus, the
first time that this header file is included, the program text in it, starting with
#define VECTOR_H and ending with the prototype of vector_add, will be in-
Revision: 6.38
8.1. Modules in C: files and the C preprocessor 253
cluded. This code actually defines VECTOR_H, so the next time that the header file
is included, the code will not be included. This is a low level mechanism indeed,
but it does the trick.
The C module mechanism really breaks down when names of functions of var-
ious modules clash. All functions of all modules essentially share the same name
space. This means that no two functions may have the same name. It is good
practice to ensure that function names are unique and clearly relate to a module,
in order to prevent function names clashing. The complex number module at the
beginning of this chapter uses names of the form complex_ ✏✒✏✒✏ to indicate that
these belong to the complex number module.
#endif /* BOOL_H */
This module only exports a type, and it has no corresponding source file. From
now on, we will import the module bool.h when booleans are needed.
Like the vector module, the interface of the boolean module has been protected
against multiple inclusion, using a #ifndef and #define.
Revision: 6.38
254 Chapter 8. Modules
inside a function have a global effect. Another consequence is that seemingly log-
ical #define directives do not have the desired effect. Consider the following
program fragment with three #define directives:
#define x 4
#define y x+x
#define z y*y
int q = z ;
In this example, occurrences of x are replaced by 4, occurrences of y are replaced
by 4+4, and occurrences of z are replaced by 4+4*4+4. So x=4, y=8, and z=24,
and not 64, as one might have expected. To prevent this kind of error, expressions
named using a #define should always be parenthesised:
#define x (4)
#define y (x+x)
#define z (y*y)
int q = z ;
The definitions above result in the value 64 for z. Note that the parentheses
around the 4 are actually not necessary, as 4 will always be interpreted as 4.
Parentheses around numbers are often omitted.
The #define can be used with arguments. The arguments are placed in paren-
theses after the identifier to be defined (spaces between the identifier and the
parentheses are illegal). An identifier defined in this way is known as a macro.
When an occurrence of the macro is replaced by its value, the arguments are sub-
stituted textually. As an example, consider the following definition, which uses
the ternary conditional expression ✏✕✏✒✏ ? ✏✒✏✕✏ : ✏✒✏✒✏ (defined in Section 2.2.5):
#define min(x,y) ( (x)<(y) ? (x) : (y) )
This states that wherever the call min( . . . , . . . ) is found, the following text
should appear instead:
( (x)<(y) ? (x) : (y) )
All occurrences of x and y are replaced by the first and second argument of min.
Thus, the text min(p,q) will be replaced by:
((p)<(q)?(p):(q))
Evaluating this expression yieldsp , which is indeed the smallest of the two
characters. The parentheses around x and y are necessary to ensure that argu-
ment substitution does not result in unexpected answers. However, despite the
parentheses, this macro still exhibits peculiar semantics. Consider the following
example:
min( p, getchar() )
This should intuitively return the minimum of the characterp and the next
character on the input stream. Unfortunately, this is not the case, as the textual
substitution results in the following expression:
( (p)<( getchar() ) ? (p) : ( getchar() ) )
Revision: 6.38
8.2. Compiling modules 255
The conditional first calls getchar and checks if the value is greater thanp . If
this is the case, the value of the expression isp . If it is not the case, getchar
is called again, which has a side effect.. This causes the final value to be the second
character on the input stream (which may or may not be greater thanp ). How-
ever, the macro min above does have an advantage over the following function
minf:
int minf( int x, int y ) {
return x < y ? x : y ;
}
The function minf works only if both arguments are of the type int. However, the
macro min works on any combination of types. The term that is used to describe
this behaviour is that in C macros are polymorphic, while functions are specialised
for certain argument types.
Although polymorphism can be a good reason to use macros, the programmer
should be aware that a macro cannot be passed to a higher order function. (Re-
member that macros are textually substituted). Macros can be quite useful, but
they have to be used with care.
Revision: 6.38
256 Chapter 8. Modules
The -c option means ‘compile this module only’. The C compiler will generate
an object-file and store this under the name vector.o. Filenames ending with
.o are object files. When modules are compiled separately, a final stage is needed
which generates an executable, given a number of object-files. This stage is called
the linking process. In order to link three objects files, say vector.o, matrix.o
and graphics.o, we call the C-compiler without the -c option. Because the ar-
guments are object files (recognised by their .o suffixes), the compiler will link
the modules together in one binary. In the example below, we have passed the -o
option to give the binary a meaningful name:
cc vector.o matrix.o graphics.o -o graphics
So all the programmer has to do in order to generate an executable is compile all
modules that need compilation, and to link the binaries together. The following
sequence of commands would achieve this on a UNIX system:
cc -c vector.c
cc -c matrix.c
cc -c graphics.c
cc vector.o matrix.o graphics.o -o graphics
These statements will compile all modules and link the binaries. Because typing
these commands and remembering precisely which modules need to be compiled
is an error-prone and tedious process, UNIX supplies a tool which does the work,
make. When invoked, make recompiles all modules that need to be recompiled. It
needs a file that specifies what to make and how to make it; this file is known as
the make file and is usually called makefile or Makefile.
Revision: 6.38
8.2. Compiling modules 257
cc -c vector.c vector.c
vector.o
vector.h
The module vector had only two dependencies, the header file and the code file.
The module matrix has more dependencies. matrix includes the header files
matrix.h and vector.h. Thus matrix must be compiled if either matrix.c is
updated (the code of the matrix module), if matrix.h is changed (to verify that
the header still matches with the code file), or if vector.h is updated (to verify
that the use of the vectors complies with the changed definition). The part of the
makefile that specifies how to compile the module matrix is:
matrix.o: matrix.c matrix.h vector.h
cc -c matrix.c
Revision: 6.38
258 Chapter 8. Modules
There are now three dependencies, the last one explaining that the target
matrix.o needs to be recompiled if vector.h has been updated.
The two parts of the make file for the modules vector and matrix can
now be concatenated to one makefile, specifying how to compile vector and
matrix. This combined makefile is shown below. We have placed multiple de-
pendencies on a single line:
vector.o: vector.c vector.h
cc -c vector.c
cc -c vector.c vector.c
vector.o
vector.h
cc -c matrix.c matrix.c
matrix.o
matrix.h
Note that there are two arrows pointing to the file vector.h. If vector.h is
updated then both modules vector and matrix have to be recompiled.
The third module that was used was graphics. The module graphics in-
cludes the header files for matrix.h, vector.h, and graphics.h. Creating the
rules for graphics is left as an exercise to the reader.
Exercise 8.1 Give the rules for the makefile that would describe how to compile
the module graphics.
Exercise 8.2 Integrate the rules of exercise 8.1 with the previous makefile (that
specified how to compile the modules vector and matrix) and draw the
dependency graph.
So far, this example has explained how and when modules are to be compiled.
What is missing is the linking stage. The rule for how to link the modules, assum-
ing that the final binary is called graphics, is:
graphics:
cc -o graphics vector.o matrix.o graphics.o
We must also state when graphics must be linked. In this case, the target
graphics must be made when either of the three dependencies vector.o,
matrix.o or graphics.o has changed. The dependencies are:
graphics: vector.o matrix.o graphics.o
Revision: 6.38
8.2. Compiling modules 259
Note that each of these three is a actually target of a previous rule! This is shown
when we integrate all parts of the make file:
graphics: vector.o matrix.o graphics.o
cc -o graphics vector.o matrix.o graphics.o
vector.o: vector.c vector.h
cc -c vector.c
matrix.o: matrix.c matrix.h vector.h
cc -c matrix.c
graphics.o: graphics.c graphics.h matrix.h vector.h
cc -c graphics.c
The complete dependency graph can be derived from the makefile:
cc -c graphics.c graphics.c
graphics.o
graphics.h
cc -c matrix.c matrix.c
matrix.o
matrix.h
The fact that vector.o is both a target (of the rule that specified how to compile
the module vector) and a dependency (of the rule that specified how to link the
program) is shown here as a box that has both inward and outward arrows.
The order of rules in the makefile is irrelevant but for one exception: the rule
that is specified at the top specifies the target that will be made by default.
The dependency graph is used by make to find out which modules need to be
recompiled. As an example, assume that the file matrix.c was updated recently.
By tracing the dependencies backwards, make can deduce that it must remake
matrix.o and graphics. What is more, they must be made in that order. This
process of following the dependencies is shown below. The targets graphics and
matrix.o in the grey boxes need to be remade.
Revision: 6.38
260 Chapter 8. Modules
cc -c graphics.c graphics.c
graphics.o
graphics.h
cc -c matrix.c matrix.c
matrix.o
matrix.h
Exercise 8.3 Use the dependency graph to determine which files need to be re-
compiled when matrix.h is changed.
Default rules
It is permitted to omit the rule for certain targets. In this case, the make program
uses default rules to decide how to make the target. In order to find out how to
generate a target, make will use the file-suffix of the target. Given the file-name
suffix, make will search for a default rule that specifies how to make a target.
One of the built-in default rules of make is that any file ending with .o can
be generated by invoking the C-compiler on a corresponding .c file. Other rules
state how Fortran or Pascal programs can be compiled. The advantage of this facil-
ity is twofold: the make file becomes shorter (now only giving dependencies and
the final target rule) and the programmer does not have to worry about the precise
compiler options anymore. Our example makefile would be reduced to:
graphics: vector.o matrix.o graphics.o
cc -o graphics vector.o matrix.o graphics.o
Abbreviations
In the example make file, some text fragments are repeated. As an example, the
list of objects that are needed to build the whole program is specified twice:
Revision: 6.38
8.2. Compiling modules 261
=
☎
While before we had to cut and paste the text fragment, we can now write $( ).
☎
Making dependencies
The program make is convenient to use, provided the make file is correct. If the
user makes a mistake in the make file, make will not recompile modules when
they should be recompiled. This can lead to disastrous results (especially since the
linker of C is not fussy at all about inconsistencies). Suppose that the following
dependency had been missed out:
matrix.o: vector.h
In this case, a change in the interface of the vector module will not lead to recom-
pilation of matrix.
To prevent these errors, a utility program is available that finds out which de-
pendencies exist between various C modules. This program, called makedepend,
reads a number of C source modules and generates all the dependencies that exist
Revision: 6.38
262 Chapter 8. Modules
between these modules. In its simplest form, makedepend is just invoked with all
the source modules involved. It will generate all the dependencies and write them
in the make file. As an example, consider the following make file:
OBJECTS=vector.o matrix.o graphics.o
graphics: $(OBJECTS)
cc -o graphics $(OBJECTS)
The first goal specifies how graphics is to be made; there are no dependencies.
We can now type the following command:
makedepend graphics.c vector.c matrix.c
This will cause makedepend to find all dependencies for these three source files
and to write them at the end of the make file. The resulting make file is:
OBJECTS=vector.o matrix.o graphics.o
graphics: $(OBJECTS)
cc -o graphics $(OBJECTS)
graphics: $(OBJECTS)
cc -o graphics $(OBJECTS)
depend:
makedepend graphics.c vector.c matrix.c
Revision: 6.38
8.3. Global variables 263
As long as there is no file named depend the rule for depend will always
be executed when requested. For more advanced information on make and
makedepend the reader is referred to the manual pages of these two programs.
Revision: 6.38
264 Chapter 8. Modules
pseudo random numbers can be computed. A widely used algorithm is the linear
congruential method, which uses the sequence:
✎ ✍✏☞✞✍ ✟ ✟
✂☎
✟
✟
✁
✟
✎ ✟ ✟
✂ ✒✦✥★✧ ☞✞✍ if
✱ ✪
✎ ☞ ☞ ✎
✡
☞
If and are carefully chosen ( should be a prime number and should be a ✍ ✍ ✍
☞
primitive root of ), the numbers ✟ ✟✡✂ ✟ ✏✒✏✕✏ will traverse all numbers between 1 ☞
✎
✁
✄ , then the sequence of generated numbers is (reading from left to right, top
to bottom): ✁
✆ ✄✂ ✂ ☎ ✂
✂ ✂
✁ ✁
✪ ✪
✝ ✝ ✝
✠ ☎
✂ ✂
✪
✝ ✝ ✝ ✝ ✝ ✝
✆✠ ✞✝ ✝ ✝ ✆☎ ✝ ✠ ✝ ✝ ✝
✆ ✏✒✏✒✏ ✝
The first number of this sequence is called the seed of the random number genera- ☞
tor. For this type of generator, any seed in the range ✏✒✏✕✏ will work.
Given the series ✟ , ✟✡✂ , ✟ , . . . which are numbers in the range ✏✒✏✕✏ , one
☞
✪ ✒✦✥★✧
✁
can obtain a series of numbers in the range ✏✒✏✕✏ by using the numbers ✟ ✟
. Thus for the simulation of a coin that can either be head (1) or tail (0), choose
, which gives the pseudo random sequence:
✝
✪ ✪ ✪
✪ ✪ ✪ ✪ ✪ ✪ ✪
✪ ✪ ✪ ✪ ✪
✪ ✏✒✏✒✏
This series, like the previous one, repeats itself after 30 numbers. This is the period ☞ ✎
of the random generator, defined by and . The range of the numbers is given ☞
✎
by . In a general random number generator can be chosen by the user, but
and are chosen by the designer of the module to guarantee a long period.
The SML interface of the random number module would be:
signature RANDOM = sig
type random ;
val random_init : random ;
val random_number : random -> int -> random ;
end ;
The implementation needs two values period and root that are not available to
the users of the random number generator. Here the interface places a restriction
on the SML module.
structure Random : RANDOM = struct
type random = (int * int) ;
val period = 31 ;
Revision: 6.38
8.3. Global variables 265
val root = 11 ;
val random_init
= (1,1) ;
#define period 31
#define root 11
Revision: 6.38
266 Chapter 8. Modules
#define period 31
#define root 11
Revision: 6.38
8.3. Global variables 267
inconvenient in C, while the version with state was not a good building block. A
third solution can be formulated using the following interface:
typedef struct {
int seed ;
} random ;
#define period 31
#define root 11
Revision: 6.38
268 Chapter 8. Modules
Revision: 6.38
8.4. Abstract Data Types 269
int counter ;
Functions and variables share the same name spaces and the same declaration
mechanism. That is, a function declared static is only visible to the module
where it is declared, not in any other module. Functions that are not declared
static can be used globally. Functions cannot be declared locally in a function.
Revision: 6.38
270 Chapter 8. Modules
This specifies that the type random refers to a particular structure called
random_struct, without revealing its contents. The only operations that can be
performed on this type is to declare a pointer to it and to pass it around. The mod-
ule interface becomes:
typedef struct random_struct random ;
#define period 31
#define root 11
struct random_struct {
int seed ;
} ;
Revision: 6.38
8.4. Abstract Data Types 271
The functions initialising the random number generator and calculating the ran-
dom numbers have to be changed accordingly. The interface to the rest of the
world remains the same.
Exercise 8.4 Give the SML version of the hidden state random number module.
What has been created is called an Abstract Data Type, or ADT. An ADT is a mod-
ule that defines a type and a set of functions operating on this type. The structure
of the type itself is invisible to the outside world. This means that the internal
details of how the type is organised are hidden and can be changed when neces-
sary. The only public information about the type are the functions operating on
it. Note that no other module can operate on this type, as the internal structure is
unknown.
Exercise 8.5 Sections 7.3.1 and 7.3.4, and Exercise 7.13 discussed three implemen-
tations of buffered streams: using a sliding queue, a shifting array and a
cyclic array. Design an ADT that implements a buffered stream using ei-
ther of these methods (the third implementation is the most efficient). Your
ADT should have the following declarations in the header file:
typedef struct buffer Buffer ;
The ADT above defines Buffer as the structure itself, requiring an explicit * in
each of the declarations. Alternatively, we could have declared:
typedef struct buffer *AltBuffer ;
Revision: 6.38
272 Chapter 8. Modules
Exercise 8.6 Design an abstract data type list of strings. Your ADT should have
the following declarations in the header file:
typedef struct string_list *Slist ;
Exercise 8.7 Section 5.7 presented a dynamic array. Implement a module which
defines an abstract data type for a dynamic array storing integers. The
module should at least provide functions for creating an array, changing
the bounds, and accessing an element (with a bound check).
Note that ADTs have explicit operations for creating and destroying data
structures, for example b_create and b_close for the buffered stream and
slist_cons, slist_append and slist_destroy for the string list module.
The explicit destruction clutters the code, but it is essential as the memory would
otherwise fill up with unused data structures. Many object oriented languages
have complete built-in support for data abstractions, including a garbage collector.
This is one of the reasons why many programmers prefer to use an object oriented
programming language.
Revision: 6.38
8.5. Polymorphic typing 273
Thea stands for the type parameter (‘any type will do’), and all SML functions
shown in Chapter 6 will work with this type,a list . The C typing mechanism
does not support parametrised types, but it does have a loophole that allows pass-
ing arguments of unspecified types. This means that polymorphism can be effi-
ciently supported in C, but without the type security offered by SML. In Chapter 4,
where partially applied functions were discussed, a variable of the type void *
was used to point to some arbitrary type. In C, polymorphic lists can be con-
structed using this void pointer:
typedef struct list_struct {
void *list_head ;
struct list_struct *list_tail ;
} *list ;
This type defines a polymorphic list. The first member of the list is a pointer to the
data, and the second element is a pointer to the next element of the list. With a lit-
tle care we can rebuild the full complement of list processing functions on the basis
of this new definition. To create a cons cell, the types occurring in the C function
cons have to be changed consistently from char to void * as follows:
list cons( void *head, list tail ) {
list l = malloc( sizeof( struct list_struct ) ) ;
l->list_head = head ;
l->list_tail = tail ;
return l ;
}
With this function, we can create a list of characters as before. Because the type
of the first argument of the new polymorphic cons function is void *, we must
pass a pointer to the character, rather than the character itself. To implement a
function list_hi as in Chapter 6, we might be tempted to write the following
syntactically incorrect C expression:
cons( &H, cons( &i, NULL ) ) /* ILLEGAL C */
Expressions such as &H are illegal, as C forbids taking the address of a constant.
One might try to be clever and use the following syntactically correct code instead:
list list_hi( void ) {
char H = H ;
char i = i ;
list hi = cons( &H, cons( &i, NULL ) ) ; /* INCORRECT */
return hi ;
}
This version of list_hi is syntactically correct: it is legal to take the address of
a variable and use it as a pointer. However, the resulting function is incorrect, for
the result list that is created has two dangling pointers; the address of the variables
H and i point to the local variables of list_hi, which will disappear as soon as
the return statement has been executed. Thus the problem is that the lifetime of
the list exceeds that of the variables H and i.
Revision: 6.38
274 Chapter 8. Modules
The only correct way to create the two character list is by allocating both the
cons-cells and the characters on the heap. Ideally, the allocation of the memory is
performed by cons, so that one function captures the allocation of both the cell
and the area for the data:
list cons( void *head, list tail ) {
list l = malloc( sizeof( struct list_struct ) ) ;
void *copy_of_head = malloc( /*C size of the data*/ ) ;
/*C copy the data from *head to *copy_of_head*/
l->list_head = copy_of_head ;
l->list_tail = tail ;
return l ;
}
There are two problems to be resolved here. Firstly how much store needs to be al-
located for the data? Secondly, how can we copy the data into the allocated store?
The first problem cannot be solved by the function cons, since the size of the data
pointed to by head is unknown to cons. Therefore, the size of the data must be
passed to cons as a third argument. The second problem is resolved by using a
standard function of the C library, called memcpy. Given a source pointer (head),
a destination pointer (copy_of_head), and the size of the object involved, the fol-
lowing call will copy the data from head to copy_of_head.
memcpy( copy_of_head, head, /*C size of the data*/ ) ;
Therefore, the complete code for the polymorphic cons reads:
list cons( void *head, list tail, int size ) {
list l = malloc( sizeof( struct list_struct ) ) ;
void *copy_of_head = malloc( size ) ;
memcpy( copy_of_head, head, size ) ;
l->list_head = copy_of_head ;
l->list_tail = tail ;
return l ;
}
Here is an example of its use, which builds a list of lists. The C equivalent of the
SML list [ ["H","i"],["H","o"] ] would be:
#define char_cons(c,cs) cons( c, cs, sizeof( char ) )
#define list_cons(c,cs) cons( c, cs, sizeof( list ) )
Revision: 6.38
8.5. Polymorphic typing 275
The access functions head and tail are readily generalised to polymorphic lists:
Exercise 8.8 Generalise the efficient iterative versions of functions length and
nth to work with polymorphic lists.
We are now equipped to try something more demanding. Here is the polymor-
phic version of extra_filter. The function header contains three occurrences
of void *, the first and the last correspond to the extra argument handling mech-
anism, and the second occurrence corresponds to the element type of the lists that
we are manipulating. If we cannot see the difference between these types, then
the C compiler cannot see the difference either. This means that any form of type
security has vanished by the switch to polymorphic lists.
list extra_filter( bool (*pred)( void *, void * ),
void * arg, list x_xs, int size ) {
if ( x_xs == NULL ) {
return NULL ;
} else {
void * x = head( x_xs ) ;
list xs = tail( x_xs ) ;
if( pred( arg, x ) ) {
return cons(x, extra_filter(pred,arg,xs,size), size);
} else {
return extra_filter( pred, arg, xs, size ) ;
}
}
}
Exercise 8.9 Generalise the open list version of append that makes advanced use
of pointers (See Section 6.5.3).
To complete this polymorphic list module, the function prototypes and the type
list may be specified in a header file as follows:
#ifndef LIST_H
#define LIST_H
Revision: 6.38
276 Chapter 8. Modules
8.6 Summary
The following C constructs were introduced:
#include "filename"
☞
#define ( , ...) ✟ ✂ ✁
#endif /* */
Revision: 6.38
8.6. Summary 277
Here, and are identifiers, and are parameters of the macro being de-
✟ ✂
fined, and is the replacement text for the macro. If there are no parameters,
✁
the parentheses should be omitted. The replacement text and parameters are
usually enclosed in parentheses otherwise macro substitution would end in
chaos.
Global variables Global variables are variables that are declared outside the
scope of a function. Every function can read or write them. They may even
be visible outside modules, if another module, either deliberately or acciden-
tally, uses a variable with the same name.
Static functions and variables Static variables have a life time as long as global
variables, but they are not visible outside a module or function. Static func-
tions are functions that are private to a module. Variables and functions are
declared static by prepending their declaration with the keyword static.
...
} ;
The clients importing the header can only pass values of type around, only
✁
Only those items that are really needed by the outside world should be ex-
ported by defining such items in an interface. Types and internal functions
should be confined to the implementation of the module.
✁
An Abstract Data Type, ADT, specifies a type and a set of operations. The
internal workings of an ADT are shielded off by the module. This localises
design and maintenance effort, and gives modules that are easily reused.
Revision: 6.38
278 Chapter 8. Modules
Modules using polymorphic data types can be built in C using the void
pointer. They are only type safe if the programmer invests in runtime checks.
Efficient polymorphic data types in C can only be built in a type unsafe way.
1 1
2 2
3 6
4 24
5 120
6 720
7 5,040
8 40,320
9 362,880
10 3,628,800
11 39,916,800
12 479,001,600
13 6,227,020,800
14 87,178,291,200
The function values grow so quickly that, with 32-bit arithmetic, overflow
occurs for . Thus a table of 12 values is sufficient to maintain the entire
✂
Exercise 8.11 Exercise 8.7 required the implementation of a dynamic array of in-
tegers. Define a module that implements a polymorphic dynamic array.
Upon creation of the array, the size of the data elements is passed, subse-
quent functions (for indexing and bound changes) do not need extra pa-
rameters.
Revision: 6.38
8.7. Further exercises 279
Exercise 8.12 Write a higher order polymorphic version of the quicksort function
from Chapter 7 that takes a comparison function as an argument so that it
can sort an array of any data type.
Exercise 8.13 A snoc list is an alternative representation of a list, which stores the
head and the tail of a list in reverse order. Here is the SML definition of the
snoc list:
datatype a snoc_list = Snoc of (a snoc_list * a)
| Nil ;
Three numbers 1, 2 and 3 could be gathered in a snoc list as follows:
Snoc(Snoc(Snoc(Nil,1),2),3) ;
An equivalent ordinary list representation would be:
Cons(1,Cons(2,Cons(3,Nil))) ;
This uses the following definition of a polymorphic list:
datatype a list = Nil
| Cons of (a * a list) ;
Note that in both representations the order of the elements is always 1, 2, 3.
Here is the C data structure that we will be using to represent snoc lists:
typedef struct snoc_struct {
void * snoc_tail ;
struct snoc_struct * snoc_head ;
} * snoc_list ;
(a) Define the C functions snoc, head and tail similar to those at the
beginning of this chapter 6, but associated with the type snoc_list.
(b) Create a higher order C function sprint with the following proto-
type:
void sprint(void (*print) ( void * ), snoc_list l ) ;
The purpose of this function is to print all elements of the snoc list
and in the correct order. Use a comma to separate the elements when
printed.
(c) Write a main function to test your snoc list type and the associated
functions.
✎ ✎
structure represents an -ary tree with integer values at the leafs. Here we
have chosen to use the snoc list from the previous exercise.
datatype ntree = Br of (ntree snoc_list)
| Lf of int ;
Revision: 6.38
280 Chapter 8. Modules
2 3 4
Exercise 8.15 In Exercises 8.13 and 8.14 we have created a data structure, and sets
of functions operating on the data structure. Package each data structure
with its associated functions into a separate C module.
Revision: 6.38
8.7. Further exercises 281
(a) Create a C header file and a C implementation for the snoc list data
type and the functions snoc, head, tail and sprint.
(b) Create a C header file and a C implementation for the ntree data type
and the functions nlf, nbr and nprint.
(c) Write a program to test that your modules are usable.
Revision: 6.38
282 Chapter 8. Modules
Revision: 6.38
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Chapter 9
In this chapter we will make three case studies to put in practice the principles
and techniques introduced before. The first case study is a small program to draw
a fractal using X-windows. The second study is a device independent graphics
driver, using X-windows, PostScript, and possibly some other graphics output de-
vices. The third case study is an interpreter for an elementary graphics descrip-
tion language, called gp. The gp interpreter uses the device independent graphics
driver of the second case study.
The three case studies are increasingly more difficult, and the amount of detail
left to the reader also increases. The first case study spells out the data structures
and algorithms to be used. The second case study gives the data structures and
some code. The third case study only suggests the data structures that might be
used. The reader is encouraged to work out these case studies.
All programs use X-windows to render the graphics. Being able to use the X-
windows library is at the same time a useful skill and a good test of one’s program-
ming abilities. We choose the X-windows system because it is widely available; it
runs on PC’s, workstations and the Macintosh; and it is public domain software.
The disadvantage of choosing the X windows system is that it is a complex system,
especially for the beginning programmer. For this reason, we use only an essen-
tial subset of X. The interested reader is referred to the X-windows programming
manuals for complete details [17].
283
284 Chapter 9. Three case studies in graphics
✍ ✍
✆ ✍
✆
✪
✁ ✄
✆
✁ ✁
✁
✂
✆
✁
✄
✍ if
✱ ✪
✆
For some choices of the point , the series diverges. Take the following two ex-
✁
✄
✪✍ ✁✍ ✝✍ ✁✪✍ ✍ ✏✒✏✕✏
✄
✆
✂✆✠✄✂✆☎
✪✍ ✍ ✍ ✍ ✍ ✏✒✏✒✏
✄
✆
✂ ✞ ✂ ✂
✂ ✂ ✝
✆ ✂
✆ ✆
✄
which the series converges. If the set of points is plotted in the complex plane, a
pretty picture results.
The program that we are going to develop will calculate an approximation to
the Mandelbrot set. To decide whether a point
✆
belongs to the set, it would be nec-
essary to compute all points of the series , which is an infinite task. We are going
to approximate this by calculating only a fixed length prefix of each series. If this
prefix does not diverge, then we assume that the infinite series does not diverge.
The approximation can be improved by increasing the number of steps, but at the
expense of extra run time.
The solution to the Mandelbrot problem is first given in SML. We will also need
the complex arithmetic module from Chapter 8. The function that decides whether
a point belongs to the Mandelbrot set is:
(* in_Mandelbrot : Complex.complex -> bool *)
fun in_Mandelbrot c
= let
open Complex
val MAXSTEPS = 31
fun step i z
= let
val z = complex_sub (complex_multiply z z) c
in
if complex_distance z > 1.0
then false
Revision: 1.25
9.1. First case study: drawing a fractal 285
#define MAXSTEPS 31
while( true ) {
if( complex_distance( z ) > 1.0 ) {
return false ;
} else if( i > MAXSTEPS ) {
return true ;
}
z = complex_multiply( z, z ) ;
z = complex_sub( z, x ) ;
i++ ;
}
}
The interface of this module to the external world is:
#ifndef MANDELBROT_H
#define MANDELBROT_H
#include "bool.h"
#include "complex.h"
extern bool in_Mandelbrot( complex x ) ;
#endif
The function in_Mandelbrot is exported, but MAXSTEPS is not exported. This
hides the details of how accurate the approximation is, so that the approximation
can be changed without having to change functions that use in_Mandelbrot.
Revision: 1.25
286 Chapter 9. Three case studies in graphics
Network
Displays
Windows
Pixels
This particular configuration shows three computers, two of which have a dis-
play. The computers are connected via a network. An X application can run on any
computer (whether it has a screen or not) and can use any number of displays on
the network. As an example, a multi user game could run on the computer in the
middle and use the two displays connected to systems on the left and right. Sim-
ple applications, like an editor, will often use only one display and will execute on
the machine connected to that display.
Within a display, an X-application distinguishes one or more windows that it
controls. Most applications will use a single window, but an application can con-
trol an arbitrary number of windows. Within a window, the application program
can do whatever it wants; the X-server will not allow an application to write out-
side its window to avoid destroying other windows, nor will it allow writing in
parts of a window that are obscured by other windows.
Each window of an X application is composed of a grid of dots, or pixels. Each
dot has a specific colour. On a monochrome display, a dot can be ‘black’ or ‘white’,
but most modern computers have a colour screen, allowing a pixel to have any of
a large range of colours. Each pixel on the grid is addressed with a coordinate.
The coordinate system is slightly unusual: the origin is in the top left hand corner,
the X coordinates run from left to right, and the Y coordinates run top-down. So the
✁ ✍ ✟
down is pixel ✪, and so on. The X library provides functions, for example, to
fill a rectangular area of dots with a colour, or to render some characters.
Given that an X application may use multiple displays and windows, the spe-
cific window and display to be used must be specified when performing some
graphics operation. This information is not stored implicitly in the library, but
must be explicitly passed by the program. This might be confusing at first because
many applications use only one display and one window, but it makes the library
more general. The X designers could have stored a “Current display” and “Cur-
rent window” in global variables to shorten the argument list, but have (in our
opinion rightly) chosen to avoid the global state.
Revision: 1.25
9.1. First case study: drawing a fractal 287
In addition to specifying which display and window to use, all functions us-
ing graphics capabilities must indicate how the graphics operations are to be per-
formed. This gives, for example, the colour and font to use when drawing a string
of text. This is passed via an object known as a graphics context. Again, the de-
signers of the X-windows system have chosen to pass this explicitly, as opposed to
maintaining a current font or current colour in a global variable. A graphic con-
text can be dynamically created and destroyed, and there are functions available
to modify certain fields in a context.
Now that we know the morphology of a window in the X-windows system, it
has to be decided how to draw the Mandelbrot set on the screen. The module in
the previous section defines, for any point in the complex plane, whether the point
is part of the (approximated) Mandelbrot set. In a window, we can only display
part of the complex plane, and we can only show it with a finite resolution, given
by the number of pixels. The figure below shows the Mandelbrot set drawn with a
✝✄✠ ✝✆✠ to ✝✄✠ ✂ ✝✄✠ , with
✪ ✆ ✪
high resolution (left) and a window (right) from
✝ ✝ pixels on this window.
✏ ✏ ✏ ✏
To display the Mandelbrot set, a mapping must be devised from the inte-
✁ ✴✍ ✟
✁ ✄✂
ger coordinates
✝✆ ✆
of the window to the complex plane. Assuming that the window
pixels, with the middle of the window on position ✂
measures ☎ , and ✆ ✟ ✂
✂ ☞
showing an area of the complex ✂
plane
is related to and as follows:
with size , then the complex number ✞ ✍
✴✍ ✍ ✍ ✂☎
✄
✄✂ ✞✆
✍☞ ✍ ✍ ✍ ✍
✟ ✂
☎
✞ ✍
✂ ✝
✆✂ ✞
✝
☞
✟ ✞
✂☎
✂ ✂ ✍
✆
✍
✝
The C implementation of this specification is shown below. The identifiers
✆
have
been given slightly more descriptive names (WIDTH ✟☎ , HEIGHT , width
✞, and height ): ✍
static
Revision: 1.25
288 Chapter 9. Three case studies in graphics
Revision: 1.25
9.1. First case study: drawing a fractal 289
Revision: 1.25
290 Chapter 9. Three case studies in graphics
The final part of the program is the main function below. It performs three
tasks. First the X-windows library is initialised and queried for the display and
window. After that, the user is queried which part of the Mandelbrot set should
be shown, and finally the Mandelbrot is drawn.
#define WIDTH 100
#define HEIGHT 100
Revision: 1.25
9.1. First case study: drawing a fractal 291
over many details here. Example widgets are scroll-bars, buttons, and text editors.
A typical X-application consists of a hierarchy of widgets (widgets can be com-
posed of other widgets), but as we hardly use any feature of widgets in this trivial
example, we do not discuss it. The interested reader is referred to the X-windows
manuals [17].
We apply the function XtVaSetValues to set the height and the width of the
widget and draw the widget by calling the function XtRealizeWidget. This cre-
ates a window on the display.
To find out what the identification of the display and window are, the X-
toolkit provides the functions XtDisplay and XtWindow. Given the display,
we use a further three functions to determine how the colours black and
white are denoted on this particular display. All arguments are then passed to
draw_Mandelbrot for drawing the Mandelbrot graphic. Good test values for the
program are:
0 0 2.5 2.5
These values correspond to a square area of the complex plane with lower left co-
ordinate ✏ ✄✠
✝ ✆✠ and upper right coordinate ✆✠
✏ ✝ ✏ ✝ ✂ ✆✠ .
✏ ✝
After the Mandelbrot graphic is drawn, the program should not terminate di-
rectly, as it would cause the window to disappear. One way of solving this would
be to include a non terminating statement at the end, for example:
while( true ) { ... }
However, this would cause the program to use the processor continuously, which
is a waste of resources. The X-toolkit provides a library function that will prevent
the application program from terminating, XtAppMainLoop. This function does
something more important: it also handles all events (mouse clicks, key strokes)
that are being sent to the application program. The purpose of the main loop is
shown later in Section 9.1.4.
To compile the program, the four functions above must be completed with a
list of include directives that import the right header files:
#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include "complex.h"
#include "mandelbrot.h"
Furthermore, a Makefile is needed, and during the linking stage of the program,
we need to tell the compiler to use the X11, X-toolkit, and mathematical libraries:
OBJECTS= main.o complex.o mandelbrot.o
mandelbrot: $(OBJECTS)
$(CC) -o mandelbrot $(OBJECTS) -lXt -lX11 -lm
depend:
makedepend $(CFLAGS) main.c complex.c mandelbrot.c
Revision: 1.25
292 Chapter 9. Three case studies in graphics
The exact calling syntax for the compiler and linker is machine dependent.
On some machines the user will have to add include and or library path op-
tions to the compiler. For example, the compiler might require an option
-I/usr/local/X11/include, and the final linking stage might need the op-
tion -L/usr/lib/X11. The local documentation may give more details on how
X11 is installed on the machine.
Compiling and executing this program will draw a small Mandelbrot graphic.
Stopping the program in its present form is a bit of a problem, as the program will
wait indefinitely. Interrupting the program or destroying the window are the only
solutions; we will resolve this problem later.
Depending on the speed of your computer system, you can choose to increase
the size of the window (WIDTH and HEIGHT) or the accuracy of the approximation
to the Mandelbrot set (by increasing MAXSTEPS).
Exercise 9.1 What would happen if the C-preprocessor directives that define
WIDTH and HEIGHT were defined before the first function of this module?
Revision: 1.25
9.1. First case study: drawing a fractal 293
static
void draw_Mandelbrot( double x, double y,
double width, double height) {
int X, Y ;
GC gc = XCreateGC( theDisplay, theWindow, 0, NULL ) ;
for( X=0 ; X<WIDTH ; X++ ) {
for( Y=0 ; Y<HEIGHT ; Y++ ) {
if( in_Mandelbrot( window_to_complex( X,Y,x,y,
Revision: 1.25
294 Chapter 9. Three case studies in graphics
width,height ) ) ) {
draw_pixel( gc, black, X, Y ) ;
} else {
draw_pixel( gc, white, X, Y ) ;
}
}
}
XFreeGC( theDisplay, gc ) ;
}
The function main must now be changed, such that instead of creating local vari-
ables to store, for example, the display, appropriate global variables are initialised.
int main( int argc , char *argv[] ) {
XtAppContext context ;
int theScreen ;
double x, y, width, height ;
Widget widget = XtVaAppInitialize( &context, "XMandel",
NULL, 0, &argc, argv, NULL, NULL ) ;
XtVaSetValues( widget, XtNheight, HEIGHT,
XtNwidth, WIDTH, NULL ) ;
XtRealizeWidget( widget ) ;
theDisplay = XtDisplay( widget ) ;
theWindow = XtWindow( widget ) ;
theScreen = DefaultScreen( theDisplay ) ;
white = WhitePixel( theDisplay, theScreen ) ;
black = BlackPixel( theDisplay, theScreen ) ;
printf("Enter originx, y, width and height: " ) ;
if( scanf( "%lf%lf%lf%lf", &x,&y,&width,&height ) != 4 ) {
printf("Sorry, cannot read these numbers\n" ) ;
} else {
draw_Mandelbrot( x, y, width, height ) ;
XtAppMainLoop( context ) ;
}
return 0 ;
}
The global variables introduced are assigned exactly once. They are used neither
to exchange data, nor to implicitly remember state between function calls, but they
contain globally constant values. The resulting changes to the module will require
the main module to be recompiled. The other modules (Mandelbrot, bool and
complex) have not changed.
Revision: 1.25
9.1. First case study: drawing a fractal 295
the window if something happens to it, for example if the window becomes ob-
scured by another window, or when the window is closed and reopened. Both
problems can be solved if the events of the X application are handled properly.
Each time something happens to an application program a so called event is
posted to the application. The X-library handles these events, and the program-
mer can instruct the X-library to call specific functions when certain events are re-
ceived.
As an example, we can define a function that is going to handle the Expose
event. An Expose event is posted to the application when (a part of) a window
has become visible. To receive the event, the X-library must be informed that we
wish to receive information about the event:
XtAddEventHandler( widget, ExposureMask, false,
draw_Mandelbrot, NULL ) ;
Calling this function from the main
program will cause the function draw_Mandelbrot to be called each time that
part of the window is exposed. This is a good starting point, but what we should
realise is that that draw_Mandelbrot requires arguments. We need to pass some
arguments through the X-library to draw_Mandelbrot. The X library provides
a mechanism for this: the fifth argument of XtAddEventHandler is a pointer of
type void *, which is passed on to the function. This is exactly the extra argument
mechanism that was introduced in Chapter 4.
To use the extra argument mechanism here, we need to define a structure that
will hold the arguments and an extra function to unpack the structure and pass
the arguments to draw_Mandelbrot. The structure to pass the arguments is:
typedef struct {
double x, y ;
double width, height ;
} rectangle ;
The four elements of this structure each hold one of the arguments. The function
that is called by the X-mechanism is shown below. This function has four argu-
ments. The second argument is the pointer to the structure. The type used by
X-windows is XtPointer, instead of void *, to guarantee that X programs can
also be compiled with pre-ansi C compilers. We ignore the other arguments in this
example:
void handle_expose( Widget w, XtPointer data,
XEvent *e, Boolean *cont ) {
rectangle *r = data ;
draw_Mandelbrot( r->x, r->y, r->width, r->height ) ;
}
When an expose event comes in, the entire Mandelbrot graphic is drawn. It is not
always necessary to draw the whole Mandelbrot graphic, because sometimes only
parts of the window need to be redrawn. Inspecting the e argument would allow
the program to find out exactly which parts need to be redrawn, but that is beyond
the scope of this example.
Revision: 1.25
296 Chapter 9. Three case studies in graphics
Now that this event is being handled properly, it is no longer necessary to draw
the Mandelbrot graphic in the main program, for as soon as the window has been
created, an Expose event is posted automatically by the library. This will cause
the Mandelbrot graphic to be drawn. Thus the call to draw_Mandelbrot will be
removed from the main program:
int main( int argc , char *argv[] ) {
XtAppContext context ;
int theScreen ;
rectangle r ;
Widget widget = XtVaAppInitialize( &context, "XMandel",
NULL, 0, &argc, argv, NULL, NULL ) ;
XtVaSetValues( widget, XtNheight, HEIGHT,
XtNwidth, WIDTH, NULL ) ;
XtRealizeWidget( widget ) ;
theDisplay = XtDisplay( widget ) ;
theWindow = XtWindow( widget ) ;
theScreen = DefaultScreen( theDisplay ) ;
white = WhitePixel( theDisplay, theScreen ) ;
black = BlackPixel( theDisplay, theScreen ) ;
printf("Enter originx, y, width and height: " ) ;
if( scanf( "%lf%lf%lf%lf", &r.x, &r.y, &r.width,
&r.height ) != 4 ) {
printf("Sorry, cannot read these numbers\n" ) ;
} else {
XtAddEventHandler( widget, ExposureMask, false,
handle_expose, &r ) ;
XtAppMainLoop( context ) ;
}
return 0 ;
}
When XtAddEventHandler is called, the function handle_expose is stored in
a data structure linked to the widget. The details of the store are well hidden,
but one can envisage that the Widget structure maintains a list of functions that
handle events, as follows:
Revision: 1.25
9.1. First case study: drawing a fractal 297
Widget
x,y: 13,15 EventHandler List
Event Handler:
Event: ExposureMask
func: HandleExpose
extra arguments:
x:
y:
width:
height:
The function XtAppMainLoop can find out which functions are to be notified for
which events. It does so via the variable context, which maintains a pointer to
the top level widget. The function XtAppMainLoop then calls these functions
with the appropriate extra arguments when necessary (the precise structure is
more complicated than sketched here).
Another event that we might be interested in is when the user presses a button
on the mouse. We can use this action to let the user indicate that the application
should stop. Reacting to this button requires adding another event handler to the
program:
XtAddEventHandler( widget, ButtonPressMask, false,
handle_button, NULL ) ;
The function handle_button must be defined as:
void handle_button( Widget w, XtPointer data,
XEvent *e, Boolean *cont ) {
exit( 0 ) ;
}
The call to exit gracefully stops the program.
This concludes the presentation of our first case study. It is still a relatively
small C program but it performs an interesting computation and renders its results
graphically using a windows-library. The program combines a number of good
software engineering techniques to create a structure of reusable modules. The
program can be extended to enhance its functionality, for example, to use colour
instead of black and white rendering, or to zoom in on certain areas of the complex
plane.
Exercise 9.2 Modify the Mandelbrot program to draw using different colours. Use
the number of steps made to decide which colour to use. The Mandelbrot
Revision: 1.25
298 Chapter 9. Three case studies in graphics
set itself stays black, the area outside the Mandelbrot set will be coloured.
Here are a few functions to use from the X-windows library:
DefaultColormap( d, s ) returns the colour map of screen s on dis-
play d.
XAllocColorCells( d, c, 1, NULL, 0, col, n ) When given a
display d, a colour map c, and an array col of n longs, this function
will allocate n colours and store them in the array col. The function
XAllocColorCells only allocates colours, it does not fill them with
specific colours. This means that each of the longs in the array is now
initialised with a value which refers to an as yet undefined colour.
XStoreColor( d, c, &colour ) This function fills one particular
colour. The variable colour is a structure of type XColor which has
the following members:
long pixel This should be the reference to the colour that you want
to set.
unsigned short red, green, blue These are the values for the
red, green, and blue components: 0 means no contribution of that
colour and, 65535 means maximum; 0,0,0 represents black; 65535,
65535, 65535 represents white.
int flags This field informs which values to set in the colour table,
The value DoRed | DoGreen | DoBlue sets all colours.
Here is an example of a function that manipulates the colour map:
#define NCOLOURS 1
static unsigned long cells[NCOLOURS] ;
Revision: 1.25
9.2. Second case study: device independent graphics 299
9.2.1 PostScript
PostScript is a programming language. A PostScript program is specified in
ASCII. In order to render the graphics, the program need to be interpreted.
PostScript interpreters are found in printers, and in many windowing systems.
Note the difference with X: X graphics are drawn by calling a C function,
PostScript graphics are drawn by generating a bit of PostScript code. Consider
the following PostScript program:
%!PS
newpath
0 0 moveto
0 100 lineto
100 100 lineto
100 0 lineto
closepath
stroke
showpage
The first line %!PS declares this as a PostScript program. The second line starts a
new line drawing, called a path in PostScript. The moveto command moves the
current position to the point with coordinates (0,0).
PostScript is a stack based language. This means that arguments are listed before
giving the name of the function for which the arguments are intended. PostScript
uses the standard Cartesian coordinate system, with the lower left corner of the
area that can be used for drawing at (0,0). This is thus different from X-windows,
which uses the top left hand corner of the image as point (0,0). Such differences
will be hidden by the device independent driver that we are about to develop.
In the PostScript program above, the first lineto command defines a line
Revision: 1.25
300 Chapter 9. Three case studies in graphics
from the current point, (0,0), to the new point (0,100). The next lineto defines
a line from the current point, (0,100), to the point 100,100. The closepath com-
mand closes the path (defining a line from the end to the starting point). The
stroke command actually draws a line through all the points along the path that
we have specified. The final showpage command causes the output device to start
rendering all drawing and text on the current page.
The PostScript unit is a point, which is
✂ of an inch. The PostScript program
above thus draws a square with its south west corner at the origin of the coordi-
nate system. The square has sides slightly larger than 1 inch.
In all our examples we will print PostScript code in an output file. To render
such files, it must be viewed using a PostScript viewer (ghostview for example),
or it must be sent to a PostScript compatible printer. We will not discuss PostScript
in any more detail here, we refer the interested reader to the PostScript Reference
Manual [4].
✟ ✂ lineto
Here ✟✡✂ ✂ and ✟
✁ ✍ ✟ ✁ ✍ ✟
✂ are the (real) coordinates of the begin and the end point of
✂
the line. The data structures and function that will draw a line in either PostScript
or X-windows should take into account the peculiarities of both X-windows and
PostScript. Here are the appropriate data structures, where X11 represents the X-
windows library and PS the PostScript format:
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <stdio.h>
Revision: 1.25
9.2. Second case study: device independent graphics 301
XtAppContext context ;
Widget widget ;
} X11 ;
struct {
FILE *out ;
} PS ;
} c ;
} Device ;
The type Device captures the information about the device. In this case there
are only two devices: X11, or PS. Specific information pertaining to each device
is needed in the structure. For X-windows the display, window, graphics con-
text, application context and the widget to be used for graphics operation must
be stored. For PostScript we store a file descriptor.
The line drawing function itself performs case analysis to decide which output
device is appropriate:
void draw_line( Device *d,
int x0, int y0, int x1, int y1 ) {
switch( d->tag ) {
case X11:
XDrawLine( d->c.X11.d, d->c.X11.w, d->c.X11.gc,
x0, X11HEIGHT-1-y0, x1, X11HEIGHT-1-y1 ) ;
break ;
case PS:
fprintf( d->c.PS.out, "newpath\n" ) ;
fprintf( d->c.PS.out, "%d %d moveto\n", x0, y0 ) ;
fprintf( d->c.PS.out, "%d %d lineto\n", x1, y1 ) ;
fprintf( d->c.PS.out, "closepath\n" ) ;
fprintf( d->c.PS.out, "stroke\n" ) ;
break ;
default:
abort() ;
}
}
The function draw_line takes the device parameter, and uses the tag to find out
whether to draw a line on the X screen, or whether to output PostScript code. The
code also does some transformations: X11 draws upside down (low Y values are
at the top of the drawing), while a low Y value in PostScript represents something
at the bottom of the drawing. These differences are hidden by the device driver.
The example can be extended with other devices and more functions. For ex-
ample, the function shown below draws a box. The subtractions made in the ar-
guments to XDrawRectangle show how the device independence hides the dif-
ferences between the PostScript and X11 views on the coordinate systems:
void draw_box( Device *d,
int x0, int y0, int x1, int y1 ) {
switch( d->tag ) {
Revision: 1.25
302 Chapter 9. Three case studies in graphics
case X11:
XDrawRectangle( d->c.X11.d, d->c.X11.w, d->c.X11.gc,
x0, X11HEIGHT-1-y0, x1-x0, y1-y0 ) ;
break ;
case PS:
fprintf( d->c.PS.out, "newpath\n" ) ;
fprintf( d->c.PS.out, "%d %d moveto\n", x0, y0 ) ;
fprintf( d->c.PS.out, "%d %d lineto\n", x0, y1 ) ;
fprintf( d->c.PS.out, "%d %d lineto\n", x1, y1 ) ;
fprintf( d->c.PS.out, "%d %d lineto\n", x1, y0 ) ;
fprintf( d->c.PS.out, "%d %d lineto\n", x0, y0 ) ;
fprintf( d->c.PS.out, "closepath\n" ) ;
fprintf( d->c.PS.out, "stroke\n" ) ;
break ;
default:
abort() ;
}
}
The extension of the device independent driver with a box primitive is not much
work. However, each time that another device has to be added, both functions
draw_line and draw_box need to be modified. This is not much work in the
case of two functions, but with 15 primitive elements it becomes clear that this so-
lution lacks structure: each function contains information about every device. This
means that information about one particular device is scattered over all functions,
and implementation of a new device will require the modification of all functions.
✟ ✟ ✂ ✂
✁ ✍ ✟ ✁ ✍ ✟
✟ ✟✡✂ ✂
✁ ✍ ✟ ✁ ✍ ✟
✟
✁ ✍ ✟
To manipulate a set of functions such as those listed above we need a data struc-
ture that holds them together. In SML we could define the data structure as fol-
lows:
datatype a graphics
= DrawBox of (a * int * int * int * int -> a)
| DrawLine of (a * int * int * int * int -> a)
| DrawCircle of (a * int * int * int -> a)
| DrawEllipse of (a * int * int * int * int -> a) ;
Revision: 1.25
9.2. Second case study: device independent graphics 303
This data type stores functions to draw a box, a line, a circle and an ellipse. It takes
something of typea (an array of pixels or something else), and it produces a new
version ofa . The designer of a graphics device will have to implement the func-
tions needed to render lines, boxes, and so on, and will create a data structure of
the type graphics which contains these four functions. The user of the graph-
ics library will simply use one of the available graphics structures, and call the
functions.
The SML data structure is not complete; an extra open function is needed to
create an initial value fora , and also a close function is needed. The full defini-
tion of the SML device driver would read:
datatype (a,b,c) graphics
= Open of (b -> a)
| DrawBox of (a * int * int * int * int -> a)
| DrawLine of (a * int * int * int * int -> a)
| DrawCircle of (a * int * int * int -> a)
| DrawEllipse of (a * int * int * int * int -> a)
| Close of (a -> c) ;
Hereb is a device dependent data type containing information on how to open
the device (for example the size and position of a window, or the filename for a
postscript file). Similarlyc is a device dependent type containing any informa-
tion that remains after the last object has been drawn.
The graphics structure can be translated into C, where we use the state hid-
ing principles of the previous chapter. The function open will allocate the state,
and return a pointer to it, the other functions will receive this pointer, and modify
the state when appropriate. The close function in C explicitly deallocates stor-
age, because C has explicit memory management.
typedef struct {
void *(*open)( void *what ) ;
void (*draw_line)( void *g, int x0, int y0,
int x1, int y1 ) ;
void (*draw_box)( void *g, int x0, int y0,
int x1, int y1 ) ;
/*C Other elements of device driver*/
void (*close)( void *g ) ;
} graphics_driver ;
Exercise 9.3 Add support for drawing circles and ellipses to the device driver
structure.
Exercise 9.4 What is the most important difference between the SML and the C
data structure?
For any specific graphics library we need to specify the functions to perform these
primitive operations, and a structure containing the pointers to these functions.
Revision: 1.25
304 Chapter 9. Three case studies in graphics
So, we can now define the following module for the X11 driver:
#include "X11driver.h"
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
Display *d ;
Window w ;
GC gc ;
XtAppContext context ;
Widget widget ;
} X11Info ;
Revision: 1.25
9.2. Second case study: device independent graphics 305
graphics_driver X11Driver = {
X11_open,
X11_draw_line,
X11_draw_box,
/*C other functions of the driver*/
X11_close
} ;
The interface of the module exports the driver information, that is the structure
of the type graphics_driver. It also exports the definition of the structure that
defines which parameters must be passed to open the device, in this case a pointer
to the argument count and the argument vectors:
#ifndef X11_DRIVER_H
#define X11_DRIVER_H
typedef struct {
int *argc ;
char **argv ;
} X11Open ;
#endif /* X11_DRIVER_H */
Exercise 9.5 Add support for drawing circles and ellipses to the X11 device driver.
Exercise 9.6 Define the appropriate data structure and functions for the Postscript
driver.
Revision: 1.25
306 Chapter 9. Three case studies in graphics
Drivers are coded in separate modules. The advantage of this is that mainte-
nance and development of code are decoupled, and that different people can
develop drivers independently. The localisation of errors is also easier.
✁
If the device allows it, multiple instances of the same driver can be opened
simultaneously. For example, output can be generated on three postscript
files at the same time. A design that would use global variables in the mod-
ules, for example FILE *out as a static variable in the module PostScript,
would support only one file at a time.
Revision: 1.25
9.3. Third case study: a graphics language 307
Here ✟ is the coordinate of the lower left hand corner and ✟✎✂ ✂ is
✁ ✍ ✟ ✁ ✍ ✟
✁ ✂ ✁ ✂
the coordinate of the upper right hand corner of the figure. Modify the
PostScript driver so that the bounding box is maintained (in the PSInfo
structure), and printed just before the file is closed. To be properly under-
stood by PostScript interpreters you must print a line
%%BoundingBox: (atend)
Immediately after the %!PS line in the beginning.
Exercise 9.8 The computer system that you have access to may have a native
graphics system that is different from X11 or PostScript. Develop a device
driver that interfaces to the native graphics system.
Exercise 9.9 Enhance the graphics driver to allow the use of colour.
Exercise 9.10 Rewrite the fractal program of our first case study to use the de-
vice independent graphics library. Check the PostScript output of your pro-
gram.
file gp device
Revision: 1.25
308 Chapter 9. Three case studies in graphics
Examining the picture from left to right we encounter a circle labeled with the text
file. This says that we have to create a file containing a graphics program. This
file is then read and interpreted by the graphics processor program, as symbol-
ised by the box labeled with the text gp. Finally, the result of the interpretation is
shown on an appropriate output device, which is indicated by the circle labeled
device.
The graphics language that we will use to create the picture is essentially a sim-
plified and stylised version of the paragraph of text above. The following are the
most important elements of the description:
✁
The English description mentions the graphic primitives box and circle.
✁
The texts "file", "gp" and "device" are used as labels of these three
primitives.
✁
As we are used to reading English text from left to right, we will also assume
that pictures in our graphics language are described from left to right.
With these considerations in mind, the following graphics program seems to be a
reasonable and succinct description of our picture:
.PS
circle "file" ;
line ;
box "gp" ;
line ;
circle "device"
.PE
The box and the circle are labeled by writing the text of the label (in double
quotes) next to the primitives as attributes. The two line primitives take care of
the connections. We will call a primitive with its associated attributes an element.
Then all elements are separated by semi colons and as a finishing touch, the key-
words .PS and .PE indicate the start and the end of the graphics program.
The graphics programming language as we have describe here is actually a
sub-set of the PIC language designed by Brian Kernighan [6], and our gp program
will be a tiny version of the full implementation of the PIC language.
To build an interpreter for a language is a non trivial task because it requires
the interpreter (in our case the gp program) to understand the meaning of the
words and the sentences of the language. We can identify the following tasks in-
volved in this process of understanding:
lex Recognising the words of the language, such as box, "file", ; and .PS,
where a word consists of a sequence of characters. This first task is called
the lexical analysis or lexing for short.
parse Recognising the sentences of the language, such as circle "file" ;,
where the sentences consist of sequences of words. This second task is called
parsing.
Revision: 1.25
9.3. Third case study: a graphics language 309
interpret Interpreting the individual sentences in such a way that they form part
of a whole: the picture.
Using our graphics language to describe these three tasks and their relationship
we arrive at the following structure:
In the following sections we will look at these three tasks and set a number of in-
teresting exercises. When worked out successfully they create a complete gp pro-
gram capable of drawing pictures, of which we have already seen many examples.
Several more examples will follow shortly.
symbol such as ;.
number It seems a good idea to include also real numbers in our graphics lan-
guage, as they will be handy for creating primitives of a specific size and
movements over a certain distance. An example of a number is 3.1415.
error We have now essentially decided upon the possible form of the words that
we admit to our language. However, a well designed program should al-
ways be able to deal with incorrect input. To cater for this we add a further
category of words that represent erroneous input. Some examples are: @#%$
and 3.A
Now we can define a data structure to represent all possible words, which is con-
ventionally called a token. Here is the SML version of the data structure:
datatype token = Number of real
| Ident of string
| Text of string
| Symbol of string
| Error ;
Revision: 1.25
310 Chapter 9. Three case studies in graphics
Exercise 9.11 Define a C data structure token that is equivalent to the SML
token, above. Create malloc based functions to dynamically allocate
structs of the appropriate form and contents and create a set of access
functions. Package the data structure and its house keeping functions in a
module with a clean interface.
A graphics program, like most other programs, is stored in a file as a sequence of
characters. The token data structure represents the words of the language, and
we have seen that the words are individual sequences of characters. Our next task
therefore is to write a function that when given a sequence of characters produces
a sequence of tokens: the lexical analysis.
Interestingly, lexical analysis can be described accurately using a picture of the
kind that our graphics language is able to create! Here is the process depicting
the recognition of real numbers. The circles represent the fact that the recognising
process is in a particular state and the arrows represent transitions from one state
to the next.
mantissa
✛ ✗✚✪ ✏✒✏✒✏ ✰✯
✂
✛ ✗✚✪ ✏✒✏✒✏ ✯ ✬
✌ ✂ ✌
✄
✛ ✗✚✪ ✏✒✏✕✏ ✰✯
✂
start
lex ✄
here
✛ ✗✚✪ ✏✒✏✒✏ ✰✯
✌ ✂
✛ ✗✚✪ ✏✒✏✒✏ ✰✯
fraction
✂
Recognising a real number is done by interpreting the picture as a road map and
using the input characters for directions. If we start in the state labeled lex then
we can only move to the state mantissa if a digit in the range ✗✚✪ ✏✕✏✒✏ ✰✯ is seen in ✂
the input. In the state mantissa, further digits can be accepted, each returning into
the current state. As indicated by the two remaining arrows, there are two ways
out of the state mantissa. When a full stop is seen we move to the state fraction
and when some other character is seen, we move back to the initial state lex. In
the state fraction we will accept further digits, but when encountering something
else than a digit we return to the initial state. The result of a journey using the
road map is the collection of characters that have been accepted. This collection
represents the current word or token.
Revision: 1.25
9.3. Third case study: a graphics language 311
A ‘road map’ such as the one above is called a state transition diagram. Similar
diagrams can be made for recognising the other tokens of the language; here is
✂✁
another state transition diagram that deals with layout characters, such as spaces
(shown as ) and new lines (shown as ).
start
lex
✛ ✗✄ ✂✁ ✯
here
Exercise 9.12 Draw the state transition diagrams for the recognition of ident,
text, symbol and error.
ident An identifier begins with a letter or a full stop (.), which is then
followed by zero or more letters or digits.
text A text is an arbitrary sequence of characters enclosed in double
quotes (");.
symbol A symbol can be either a semi colon (;) or an equal sign (=).
error When anything else is encountered, an error token is returned.
The state diagrams are combined into one by making the lex circle common to all
diagrams. If the individual diagrams have been designed properly, there should
be a number of arcs out of the lex circle, but no two should carry a label with
the same character attached. Furthermore, all possible characters should be dealt
with. The error token is used to achieve this:
✛ ✗ ✏✒✏✕✏
✆
start
here
lex ✛ ✗ ✄ ✝✁ ✯
✛ ✗ ✯ ☛ ☛
error ✛ ✗ ✏✯
Exercise 9.13 Write a C function lex that implements the combined state transi-
tion diagram for the lexical analyser of our graphics language. The function
lex should take its input from stdin.
Gather lex and its auxiliary functions in a module, but make sure that only
lex and the token type are exported through the interface.
Exercise 9.14 With the lexical analyser in place it should be possible to write a
small test program that reads characters from stdin to produce a sequence
of tokens. Print the tokens one per line.
Revision: 1.25
312 Chapter 9. Three case studies in graphics
9.3.2 Parsing
The lexical analysis delivers a stream of tokens, one for each call to the function
lex. So now is the time to gather selected tokens into larger units that correspond
to the sentences of the language. To make life easier for the interpreter we will de-
fine a number of data types that describe the relevant sentences and sub sentences
of the graphical language.
Here is the SML data type definition of the graphical primitives:
datatype primitive = Box | Circle | Line ;
To allow for some flexibility in the graphics language we allow for expressions that
consist of either an identifier or a number:
datatype expression = Ident of string
| Number of real ;
With a primitive we would like to associate not only its text label, but also whether
it should have a particular height, width or radius. This gives rise to the following
SML data type definition for an attribute of a graphics primitive:
datatype attribute = Height of expression
| Width of expression
| Radius of expression
| Text of string ;
We are now ready to define the data type for the elements (that is, the sentences of
the graphics language):
datatype element = Up
| Down
| Right
| Left
| Assign of string * expression
| Prim of primitive * attribute list ;
The element data type allows for six different kinds of sentences. The first four
are intended to move to a particular position before beginning to draw the next ob-
ject. The Assign constructor associates an identifier (represented here as a string
of characters) with an expression. This will enable us to give a name to an expres-
sion and refer to that name wherever the value of the expression is required. The
last element is the most interesting for it associates a graphics primitive with its
list of attributes.
Exercise 9.15 Create a module that defines the data structures in C to represent
the primitive, expression, attribute and element. These should
be heap allocated structures.
Revision: 1.25
9.3. Third case study: a graphics language 313
The corresponding list of tokens returned by our lexical analyser would be (also in
SML):
val box_tokens = [Ident ".PS",
Ident "box",
Text "\"a box\"",
Ident "height",
Number 3.0,
Ident ".PE" ];
The task of the parser is to gather the tokens in such a way that they fall in the right
place in the right (that is, primitive, expression, attribute or element)
data structure. To achieve this we will use the same technique of state transition
based recognition as with the lexical analyser. Firstly, we draw a series of road
maps. Here is the state diagram for a primitive. It shows that a primitive can only
be one of the three identifiers "box", "circle", or "line":
primitive: "box"
"circle"
"line"
expression: ident
number
"width" expression
"radius" expression
text
Revision: 1.25
314 Chapter 9. Three case studies in graphics
attribute
element: primitive
"up"
"down"
"left"
"right"
";"
Exercise 9.16 Write one parse function for each of primitive, attribute,
expression, element, and program. Each parse function should call
upon lex to retrieve token(s) from the stdin stream. Based upon the to-
ken received, your parse functions should decide what the next input sen-
tence is, and return a pointer to the struct of the appropriate type and
contents.
Exercise 9.17 Write a main function that uses lex and the parse functions to read
a gp program from stdin and to create a representation of the program
in the heap as a list of elements. Write a printing function for each of the
data structures involved and use this to print the heap representation of a
gp program.
Revision: 1.25
9.3. Third case study: a graphics language 315
9.3.3 Interpretation
We have now at our disposal a library of device independent graphics primitives
and the lexing and parsing tools to create a structured, internal representation of
a gp program in the heap. To combine these elements we need to write an in-
terpreter of the internal representation of the program, that calls the appropriate
library routines. Before we can embark on this two further explanations of how
gp works.
Firstly, the program has a notion of a current point and a current direction. By
default the current direction is right, but it can be modified by one of the com-
mands up, down, left or right.
The current point will be aligned with a particular corner or point of the next
graphical object to be drawn. Let us assume for now that the current direction is
right. The current point is then aligned with:
line The begin point of the line.
circle The left intersect of the circle and a horizontal line through its centre.
The small circles in the picture below give a graphical indication of the alignment
points.
line: direction
box: direction
circle: direction
The alignment point is thus as far away from the point to which we are mov-
ing. This also applies to moves in one of the other three directions. For example,
should we be moving upwards, the alignment point is below the object, instead of
its left.
Secondly, the gp program maintains a set of variables that control the default
dimensions of the primitive objects, as well as the default moving distances. These
variables can be set using assignment statements. The variables and their initial
settings (in inches) are:
.PS
boxht = 0.5 ;
boxwid = 0.75 ;
circlerad = 0.25 ;
lineht = 0.5 ;
linewid = 0.5 ;
Revision: 1.25
316 Chapter 9. Three case studies in graphics
moveht = 0.5 ;
movewid = 0.5 ;
textht = 0.0 ;
textwid = 0.0
.PE
The default width of a box is 0.75 inches and its default height is 0.5 inches. The
default can be overridden either by changing the value of the appropriate variable
using an assignment, or by using explicit attributes to state the sizes, so that for
example: box width 0.5 draws a square, and so does this gp program:
.PS
boxwid = boxht ;
box
.PE
The gp language can be extended with a large variety of useful constructs. The
exercises at the end of this chapter provide a number of suggestions.
9.4 Summary
Programming graphics systems is rewarding, but it also a difficult topic that is
worthy of study on its own. In this chapter we have merely hinted at some of the
possibilities of computer graphics. Three case studies have been made to show
how the principles of programming as we have developed them in this book are
used in practice. The most important practical points are:
✁
Use higher order functions when appropriate. Writing device drivers using
higher order functions simplifies the structure of the program.
✁
To create your design use a language with polymorphic types. Then translate
the design into C, using void * if necessary.
Revision: 1.25
9.5. Further exercises 317
Exercise 9.20 Extend the language so that the movements up, down, left and
right will take attributes, in the same way as a primitive takes attributes.
Exercise 9.21 Add to the elements a construct that makes it possible to repeatedly
draw a certain object or set of objects:
.PS
for i = 1 to 6 do {
box width 0.1 height 0.2 ;
right (0.05*i*i) ;
up 0.1
}
box width 0.1 height 0.2 ;
.PE
Exercise 9.22 Add a new primitive: ellipse. Also extend the set of default sizes:
.PS
ellipseht = 0.5 ;
ellipsewid = 0.75 ;
ellipse "an" "ellipse"
.PE
an
ellipse
Revision: 1.25
318 Chapter 9. Three case studies in graphics
Revision: 1.25
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Bibliography
[4] Adobe Systems Inc. PostScript language reference manual. Addison Wesley,
Reading, Massachusetts, 1985.
[5] R. Jain. The art of Computer Systems Performance Analysis. John Wiley,
Newyork, 1991.
[9] L. C. Paulson. ML for the working programmer. Cambridge Univ. Press, New
York, 1991.
[11] B. Schneier. Applied cryptography. John Wiley & Sons, Chichester, England,
second edition edition, 1996.
319
320 BIBLIOGRAPHY
[16] A. Wikström. Functional programming using Standard ML. Prentice Hall, Lon-
don, England, 1987.
[17] X-Consortium. X Window Manuals. O’Reilly & Associates, Inc., New York,
1990.
Revision: 1.25
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Appendix A
Answers to exercises
Below are the answers to a selection of the exercises in this book. For almost any
exercise, there is more than one correct answer, as there are many algorithms and
datastructures that implement a program. The answers that are presented here are
the ones that we consider the most appropriate.
Answer to 2.2: The general SML function schema for a cascade of conditionals
is:
(*SML general function schema*)
(* : ✁✄✂ -> ... ✁✆☎ -> ✁✞✝ *)
fun ✠✟✡✂ ... ✟☛☎
= if ☞ ✂
then ✌ ✂
else if ☞
then ✌
...
else if ☞
✁
then ✌
✁
else ✍ ;
The arguments of are ✟ ✂☛✏✒✏✒✏✄✟✔☎ , and their types are ✁ ✂✑✏✒✏✕✏✖✁✞☎ respectively. The ex-
pressions ☞✑✂✑✏✒✏✒✏ ☞ are predicates over the arguments of , ✌☛✂✑✏✒✏✒✏✞✌ and ✍ are expres-
✁ ✁
321
322 Appendix A. Answers to exercises
) {
return ✌
✁
;
} else {
return ✍ ;
}
}
Answer to 2.3: Here is the table of correspondence for eval, using the general
function schema of the previous exercise.
schema: Functional C
: eval eval
✁✄✂ : int int
✁✁ : char char
✁ :
✆
int int
✁ :
✄
char char
✆ ✁ : int int
✁✞✝ : int int
✟✡✂ : x x
✟ : o1 o1
✟ :
✆
y y
✟ :
✄
o2 o2
✟ : ✆
z z
☞ ✂ : o1 = "+" andalso o2 = "+" o1 == + && o2 == +
☞ : o1 = "+" andalso o2 = "*" o1 == + && o2 == *
☞ :
✆
(x * y) + z (x * y) + z
✌ :
✄
(x * y) * z : int (x * y) * z
✍ : raise Match /*raise Match*/
Revision: 6.47
323
The primes, spaces, equals, and \n in the format string are printed literally. The
%c and %d formats are replaced by character and integer representations of the ar-
guments. The first %c requires a character argument, c0; this is the character0 .
The %d format requires an integer, i0, which contains the integer representation
of0 , which is 48. The second %c requires a character argument again, cq; this
is the character which is represented by the integer 113, which isq . The last %d
requires an integer, iq, which contains the integer 113.
✁
Answer to 2.7: Proof of the hypothesis ✁ ✡ ✆✟ ✠ ✂ by induction over ☞ , where ✁
is defined according to (2.4):
Case :
✂
✂
✗ ✝ ✏
✁
✡ ✆✟ ✠ ✂ ✗ ✡ ✯
✁ ✝ ✏
✁
✁✟ ✠
✄ ✄
✗ ✯
✡ ✂ ✍ ☞✞✝✒✁✓✍
✆
✁ ✂
✗ ✂ ✯
✡ ✆✟ ✠ ✂ ✡
✁
✗ ✯
✁ ✁ ✂ div
☎
✟
✗ ✝ ✏
✁
✁ ✂ div ✆
✁ ✂ div
sqr
✁ ✁
☎ ☎
✗ ✯
✡ ✟✆✠✁
✁✟✆✠
✁
✂
✂
✂ div ☎
✆
✁ ✂ div
✡ ✟✆✠ ✂
✁
☎
✗ ✍ ☞ ✝✒✁✓✍
✞ ✂ ✄ ✄ ✯
✡ ✂ ✡ ✠✟
✁
✗ ✯
Answer to 2.8: The function square is called 4 times, and power is called 8
times. A complete trace is:
power(1.037155,19) is 1.037155 * power(1.037155,18);
power(1.037155,18) is square( power(1.037155,9) );
power(1.037155,9) is 1.037155 * power(1.037155,8);
power(1.037155,8) is square( power(1.037155,4) );
power(1.037155,4) is square( power(1.037155,2) );
power(1.037155,2) is square( power(1.037155,1) );
power(1.037155,1) is 1.037155 * power(1.037155,0);
power(1.037155,0) is 1.0.
Revision: 6.47
324 Appendix A. Answers to exercises
f f
☞ : i > n i > n
✌ : 0.0 0.0
✍ : f i + sum (i+1) n f f( i ) + sum( i+1, n, f )
Answer to 2.10: Here is an SML function square which computes the square
of a number by repeatedly summing a progression of odd numbers.
(* square : int -> real *)
fun square n
= let
fun int2odd i = real (2*i-1)
in
sum 1 n int2odd
end ;
Here is the corresponding C version of square with its auxiliary function
int2odd.
double int2odd( int i ) {
return 2*i-1 ;
}
Revision: 6.47
325
Answer to 2.12: The C version of product and factorial are given below.
The definition of int2real is the same as that defined for terminal.
double product( int i, int n, double (*f) ( int ) ) {
if( i > n ) {
return 1.0 ;
} else {
return f( i ) * product( i+1, n, f ) ;
}
}
Revision: 6.47
326 Appendix A. Answers to exercises
The translation of this function into C yields an auxiliary function to compute the
reciprocal of the factorial and the nearly_e function itself:
double recip_factorial( int i ) {
return 1.0/factorial( i ) ;
}
Revision: 6.47
327
Answer to 2.17: The general SML function schema for a cascade of conditionals
with local definitions is:
(*SML general function schema with locals*)
(* : ✁✄✂ -> ... ✁✆☎ -> ✁✞✝ *)
fun ✠✟✡✂ ... ✟☛☎
= let
val ✂ = ✂ (* ✂ : ✁
✆
✂ ✂ *) ✝
...
val ✂✁ = ✁ (* ✄✁ : ✁ ✆☎ *)
✆
✂ ✂
in
if ☞ ✂
then ✌ ✂
else if ☞
then ✌
...
else if ☞
✁
then ✌
✁
else ✍
end ;
Revision: 6.47
328 Appendix A. Answers to exercises
The ✟✡✂✡✏✕✏✒✏✄✟✔☎ are the arguments of , and their types are ✁✕✂✗✏✕✏✒✏✖✁✞☎ respectively. The
type of the function result is ✁✖✝ . The local variables of are ✂✑✏✒✏✒✏ ✂✁ ; their values are ✂ ✂
the expressions ✂✑✏✒✏✒✏ ✁ and their types are ✁ . . . ✁ ✆☎ respectively. The expressions
✆ ✆
☞ ✂✑✏✒✏✕✏ ☞ are predicates over the arguments and the local variables. The ✌ ✂✡✏✕✏✒✏✆✌ and
✁ ✁
✍ are expressions over the local variables and arguments. The corresponding gen-
eral C function schema is:
/*C general function schema with locals*/
✁✞✝ ( ✁✄✂ ✟✡✂ , ... ✁✆☎ ✟✔☎ ) {
const ✁ ✂ = ✂ ;
✆
...
const ✁ ☎ ✄✁ = ✁ ;
✆
if ( ☞ ✂ ) {
return ✌ ✂ ;
} else if ( ☞ ) {
return ✌ ;
...
} else if ( ☞
✁
) {
return ✌
✁
;
} else {
return ✍ ;
}
}
✁✟
✝ ✝
fahrenheit
fahrenheit
✄ ✂
✂
✄ ✂ ✝
Revision: 6.47
329
Answer to 2.21:
(a) The specification of the pop_count function is:
☎
pop_count ☎ ☎ ✂✑✏✒✏✒✏ ✂
✁ ✟
✁ ✁ ✁ ✁ ✁
✟ ✠
(b) Here is an SML function that implements the population count instruction:
(* pop_count : int -> int *)
fun pop_count n
= if n = 0
then 0 : int
else n mod 2 + pop_count (n div 2) ;
Schema Functional C
pop_count pop_count
✁✄✂ int int
✁✞✝ int int
✟✡✂ n n
☞ n = 0 n == 0
0 0
✌ n mod 2 + n % 2 +
pop_count (n div 2) pop_count (n / 2)
Revision: 6.47
330 Appendix A. Answers to exercises
The solution to the population count problem could be formulated better using
the bit-operators of C. The >> operator shifts an integer to the right, and the &
operator performs an and-operation on the integer. Thus pop_count could be
written as:
int pop_count( int n ) {
if( n == 0 ) {
return 0 ;
} else {
return (n & 1) + pop_count( n >> 1 ) ;
}
}
Note that because the & operator has a lower priority than the + operator, the ex-
pression n&1 needs to be in parentheses. Without the parentheses, the return value
would be n & (1 + pop_count(n>>1) ), which is different.
Answer to 2.22:
(a) The specification of the checksum function is:
✁
✁✶✎ ✎ ✎ ✎ ✟ ✎
✂ ✏✒✏✒✏ ✂ ✟
✁ ✁
checksum ✁
✟✆✠
✡
Revision: 6.47
331
Schema Functional C
checksum checksum
✁ int ✂
int
✁ int
✝
int
✟ n ✂
n
☞ n = 0 n == 0
0 0
✌ n mod 16 + n % 16 +
checksum(n div 16) checksum(n / 16)
Answer to 2.23: Here is an SML function that computes the -th Fibonacci
number:
(* fib : int -> int *)
fun fib n
= if n = 0
then 0 : int
else if n = 1
then 1
else fib (n-1) + fib (n-2) ;
Here is the C version of fib embedded in a main program:
#include <stdio.h>
Revision: 6.47
332 Appendix A. Answers to exercises
return 0 ;
} else if( n == 1 ) {
return 1 ;
} else {
return fib( n-1 ) + fib( n-2 ) ;
}
}
Answer to 2.24:
(a) The differences between the Fibonacci series and the nFib series are:
✁
The nFib series adds an extra 1 for each application of the inductive
case.
✎
(b) Here is an SML function that computes the -th nFib number:
(* nfib : int -> int *)
fun nfib n = if n = 0 orelse n = 1
then 1 : int
else 1 + nfib(n-1) + nfib(n-2) ;
Here are a few test cases for nfib:
nfib 0 ;
nfib 1 ;
nfib 7 ;
(c) Here is the C version of nfib:
int nfib( int n ) {
if( n == 0 || n == 1 ) {
return 1 ;
} else {
return 1 + nfib( n-1 ) + nfib( n-2 ) ;
}
}
(d) Here is a main program for testing the C version of nfib:
int main( void ) {
printf( "nFib\n") ;
Revision: 6.47
333
Revision: 6.47
334 Appendix A. Answers to exercises
Answer to 2.26:
✁ ✟ ✁ ✟
(a) Given the function , its derivative ☛ , and an initial approximation ,
✟ ✟ ✟
✁
newton raphson
✝✞
✍ ✢ ✁ ✟ ✢
✁ ✟ ✟
✁ ✟ ✟ if ✟ ✡ ✆
newton raphson ✞✠
✁ ✟✏✍
newton raphson ✁ ✟ otherwise
✟
☞☛
✟
✁ ✟
If ✟is further from 0 than the (small) distance allows, a subsequent ap- ✆
proximation is made. By choosing a small enough value for , the root will ✆
be determined with a high precision, but for a value of which is too small ✆
☛ are passed to the function calculating the root. This gives the following
function:
(* newton_raphson : (real->real) -> (real->real) ->
real -> real *)
fun newton_raphson f f eps x
= let
val fx = f(x) (* fx : real *)
in
if absolute(fx) < eps
then x
else newton_raphson f f eps (x-fx/f(x))
end ;
Revision: 6.47
335
✏ ✠✆☎ ✏✒✏✒✏ ).
✂
Revision: 6.47
336 Appendix A. Answers to exercises
four arguments; the arguments eps and x have the values 0.001 and 1.5,
while the arguments f and f_ have the values parabola and parabola_.
The first statement of newton_raphson is
const double fx = f( x ) ;
Because the argument f in this case is the function parabola, and x has the
value 1.5, this is effectively the same as:
const double fx = parabola( 1.5 ) ;
This results in the value 0.25. Hence, the double fx has the value
0.25. Because the absolute value of 0.25 is greater than eps, the function
newton_raphson is recursively called with the same arguments as before,
except x, which is now:
This evaluates to about 1.41667. Then the next iteration starts by com-
puting fx = f( x ), which is about 0.006944444. This is just larger
than eps, so a third call to newton_raphson is performed with x equal to
1.4142156862. . . which is correct to the first 5 digits.
(f) The Newton Raphson method does not terminate for ✟
✂ when ✆
✁ ✟
starting with ✟ . The first new point is ✟✘✂ ✂✁ , the next point
✂ ✂ ✂
✆
☎
the function.
Revision: 6.47
337
Answer to 3.3: The table below shows the correspondence between the two
versions of leap. The first column refers to the basic while-schema.
schema: Functional C
: leap leap
✁ : int int
✁✞✝ : int int
✟ : y y
☞ : y mod 4 <> 0 y % 4 != 0
✌ : y+1 y+1
✍ : y y
Revision: 6.41
338 Appendix A. Answers to exercises
schema: Functional C
✎
: euclid euclid
: 2 2
(✁✄✂ *✁✁ ): (int*int) (int,int)
✁✞✝ : int int
( ✟✡✂ , ✟ ): (m,n) (m,n)
☞ : n > 0 n > 0
✌ : (n,m mod n) (n,m % n)
✍ : m m
Answer to 3.6:
Just after executing values of
the code m n old_n
int euclid( int m, int n ) { 9 6
while( n > 0 ) { 9 6
const int old_n = n ; 9 6 6
n = m % old_n ; 9 3 6
m = old_n ; 6 3 6
while( n > 0 ) { 6 3
const int old_n = n ; 6 3 3
n = m % old_n ; 6 0 3
m = old_n ; 3 0 3
while( n > 0 ) { 3 0
return m ; 3 0
Case 1:
✂
✟✆✠
1✁ ✡ ✂ i ✗ ✁ ✯
1 ✗ ✡ ✯
fac 1 ✗ fac.1 ✯
Case n+1:
☎✟✆✠ ✂
(n+1) i ✂
✁
✡
☎
✁ ✗ ✁ ✯
(n+1) * ✡ ✟✆✠ ✂ i ✗ ✡ ✯
(n+1) * n ✁ ✗ ✁ ✯
Revision: 6.41
339
b ✗ arithmetic ✯
fac_accu 1 b ✗ fac_accu ✯
Case n+1:
b * (n+1) ✁ b * (n+1) * n ✁ ✗ ✁ ✯
(b * (n+1)) * n ✁ ✗ associativity* ✯
schema: Functional C
✎
: fac_accu fac_accu
: 2 2
(✁✄✂ *✁✁ ): (int*int) (int,int)
( ✟✡✂ * ✟ ): (n,b) (n,b)
☞ : n > 1 n > 1
✌ : (n-1,n*b) (n-1,n*b)
✍ : b b
Answer to 3.10: An efficient body of the while statement would be as shown be-
low. It is not elegant, as the statements that compute the values of m and f_m ap-
pear in two places. This makes the code difficult to maintain, since it means that a
change of the code has to be made twice.
if( f_m < 0.0 ) {
l = m ;
} else {
h = m ;
}
m = (l+h)/2.0 ;
f_m = f(m) ;
Answer to 3.11: The problem is that, before terminating, main wants to perform
an operation on the value returned by bisection. A naive inlining operation would
Revision: 6.41
340 Appendix A. Answers to exercises
duplicate this operation. The solution is to break out of the while loop using a
break, as shown below:
int main( void ) {
double x ;
double l = 0.0 ;
double h = 2.0 ;
double m ;
double f_m ;
while( true ) {
m = (l+h)/2.0 ;
f_m = parabola(m) ;
if( absolute( f_m ) < eps ) {
x = m ;
break ;
} else if( absolute( h-l ) < delta ) {
x = m ;
break ;
} else if( f_m < 0.0 ) {
l = m ;
} else {
h = m ;
}
}
printf("The answer is %f\n", x ) ;
return 0 ;
}
Note that this program can be optimised: x merely serves to bring the value of m
out of the while-loop. If every instance of m is replaced by x, the program will be a
bit shorter.
Answer to 3.12: The relationship between repeat on the one hand and the func-
tions from this chapter on the other hand is:
✎ ✎
✁ ✁
This shows that repeat combines the generation of a sequence of values with the
accumulation of the result. In the approach that we have taken in this chapter,
these two activities are separated. This increases the flexibility and the usefulness
of the individual building blocks foldr, map and --.
Revision: 6.41
341
✌
Case []:
✌
✁
foldl [] ✗ map.1 ✯
✍
✁
✗ foldl.1 ✯
foldl [] foldl.1
✟ ✟
✗ ✯
✌ ✟ ✟
Case ( :: ): ✄
✌ ✟ ✟
✁
foldl (map ( :: )) ✄
✌ ✌ ✟ ✠✟
✁
✍ ✌ ✟ ✟
✁
✍ ✍ ✟ ✟ ✍
hypothesis
✁
foldl ( ( )) ✄ ✗ ✯
✍ ✟ ✟
✁
foldl ( ) ✄ ✗ .1 ✯
foldl.2 ✠✟
✁
foldl ( : ) ✄ ✗ ✯
Revision: 6.41
342 Appendix A. Answers to exercises
Case []: ✁
foldl (filter
✌ ☞ [])
✁
foldl
✁
[] ✌ ✗ filter.1 ✯
✁
✗ foldl.1 ✯
foldl ✍ [] ✗ foldl.1 ✯
Case ( : ) and
✟ ✟= false:
✄
✁
☞ ✟
foldl (filter
✌ ( :: )) ✁
☞ ✟ ✟ ✄
foldl (if ✌ ☞ ✟
then ::filter ✟ ☞ ✟ ✄
hypothesis
✁
foldl ✍ ✟ ✄ ✗ ✯
foldl (if ✍ ☞ ✟
then ✌ ✟
else ) ✟ ✄ ✗ ☞ ✟ = false ✯
foldl ( ) ✍ ✍ ✟ ✟ ✄ ✗ ✍.1 ✯
foldl ( : ) ✍ ✟ ✟ ✄ ✗ foldl.2 ✯
Case ( : ) and
✟ ✟= true:
✄ ☞ ✟
foldl (filter
✌ ( :: )) ☞ ✟ ✟ ✄
foldl (if ✌ ☞ ✟
then ::filter ✟ ☞ ✟ ✄
else filter ) ✁
☞ ✟ ✄ ✗ filter.2 ✯
foldl ( ::filter ) ✌ ✟
✁
☞ ✟ ✗ ☞ = true
✟ ✯
hypothesis
✁
foldl ( ) ✍ ✌ ✟ ✟ ✄ ✗ ✯
foldl (if ✍ ☞ ✟
then ✌ ✟
else ) ✟ ✄ ✗ ☞ = true
✟ ✯
foldl ( ) ✍ ✍ ✟ ✟ ✄ ✗ ✍.1 ✯
foldl.2 ✠✟
✁
foldl ( : ) ✍ ✟ ✟ ✄ ✗ ✯
Revision: 6.41
343
Revision: 6.41
344 Appendix A. Answers to exercises
Revision: 6.41
345
Revision: 6.41
346 Appendix A. Answers to exercises
Revision: 6.41
347
parts of d. Each of these parts occupies one storage cell. The structure structx
will occupy 5 cells, and the union unionx requires two cells:
d ✁
, d , or p.y
✂
d ✂
p.x
p.y
Answer to 4.2: The SML datatype for a point in 2-dimensional space is defined
in the following way:
datatype coordinate = Polar of real * real |
Cartesian of real * real ;
The equivalent C type-declaration is:
typedef enum { Polar, Cartesian } coordinatetype ;
typedef struct {
coordinatetype tag ;
union {
struct {
double x ;
double y ;
} cartesian ;
struct {
double r ;
double theta ;
} polar ;
} contents ;
} coordinate ;
For an argument, say p, of the type coordinate, the type of the coordinate is
stored in p.tag. If the type of the coordinate is Polar, then the radius is stored in
p.contents.polar.r, and the angle is stored in p.contents.polar.theta.
If it is a Cartesian coordinate, the X and Y-coordinates are stored in
p.contents.cartesian.x and p.contents.cartesian.y.
Revision: 6.37
348 Appendix A. Answers to exercises
Answer to 4.4: The type pointer to pointer to double can be defined as follows:
typedef double ** pointer_to_pointer_to_double ;
Answer to 4.5: The type type0 is a structure containing two elements: one is a
pointer to a floating point value x, the other is an integer y. The type type1 is a
pointer to a type0 structure.
the definition of the prefix-& operator). For all for which & is legal, *&
✟ ✟ ✟
and denote the same value. In the example functions, the legal values for
✟ ✟
are i, p, q, and r.
(c) The expressions &*p and p denote the same value in almost all cases. In our
example, p is a pointer to i, so the object *p has an address, namely the ad-
dress of i. This is the same value as p. In cases where p contains an illegal
address (they will be explained later), the expression *p is illegal, hence &*p
does not have the same value.
✆ ✆ ✆
(d) The expressions &* and are only equal if is a legal pointer-value. In any
✆
other case, for example, if is the integer 12 or some illegal pointer value,
the expressions are not equal. In the example functions, the equality is true
✆
for equals p, q, or r.
typedef struct {
double x ;
double y ;
} point ;
Revision: 6.37
349
point r0 = { 1, 0 } ;
point r1 = { -0.1, -0.3 } ;
point r2 = { 0.7, -0.7 } ;
rotate( &r0 ) ;
print_point( &r0 ) ;
rotate( &r1 ) ;
print_point( &r1 ) ;
rotate( &r2 ) ;
print_point( &r2 ) ;
return 0 ;
}
Pay special attention to the implementation of rotate. Because both members of
the input are necessary in the computation of both output members, one needs to
introduce a temporary variable, ty. It can be implemented in a language with a
multiple assignment as:
(p->y,p->x) = (-p->x,p->y) ;
Answer to 4.10:
(a) The * operator expects a pointer and dereferences the pointer. This yields
the value pointed at. The & operator expects a value that it can work out the
address of and returns that address as a pointer. The * operator could be be
viewed as undoing the effect of the & operator.
(b) The answer printed by the main function is two times the number 3, then
the number 1 (for true), the number 3, two times the number 1 (for true), and
again the number 3.
(c) The program would print 4 as a result of the following function calls. Firstly:
twice( add, 2 ) == add( add( 2 ) )
Secondly:
add( add( 2 ) ) == add( 3 )
And finally:
add( 3 ) == 4
(d) The differences are the following:
✁
Program (c) can handle only functional arguments with one argument;
Program (d) can handle only functional arguments with two arguments.
(e) The differences are the following:
✁
Program (e) can handle functional arguments with any number of argu-
ments and is thus more general than either Program (c) or Program (d).
✁
Revision: 6.37
350 Appendix A. Answers to exercises
Answer to 4.13:
(a) Below we define a point, and a bounding box in terms of two points:
typedef struct { int x, y ; } point ;
typedef struct { point ll, ur ; } bbox ;
(b) The function to normalise makes a bounding box, and ensures that the ll
field contains the minimum ✟ and values, while the ur contains the maxi-
✂
mum values:
bbox normalise( bbox b ) {
bbox r ;
if( b.ll.x < b.ur.x ) {
r.ll.x = b.ll.x ;
r.ur.x = b.ur.x ;
} else {
r.ur.x = b.ll.x ;
r.ll.x = b.ur.x ;
}
if( b.ll.y < b.ur.y ) {
r.ll.y = b.ll.y ;
r.ur.y = b.ur.y ;
} else {
r.ur.y = b.ll.y ;
r.ll.y = b.ur.y ;
}
return r ;
}
(c) The combining function returns the minimum x and y values from the two
ll points of the input boxes in the new ll point, and the maximum in the
ur point:
bbox combine( bbox b, bbox c ) {
bbox r ;
r.ll.x = b.ll.x < c.ll.x ? b.ll.x : c.ll.x ;
r.ll.y = b.ll.y < c.ll.y ? b.ll.y : c.ll.y ;
r.ur.x = b.ur.x > c.ur.x ? b.ur.x : c.ur.x ;
r.ur.y = b.ur.y > c.ur.y ? b.ur.y : c.ur.y ;
return r ;
}
Note the use of the conditional operator ✏✒✏✒✏ ? ✏✒✏✒✏ : ✏✒✏✒✏ to return the mini-
mum/maximum value.
(d-g) Below are all the graphic elements. Note that we have defined separate
types for line, rect and bbox, although they all have the same members.
These data types serve different purposes and therefore it is better to make
them separate types.
typedef struct { point from, to ; } line ;
typedef struct { point centre ; int radius ; } circ ;
Revision: 6.37
351
Revision: 6.37
352 Appendix A. Answers to exercises
Revision: 6.37
353
Answer to 5.2: An SML function to return the data of the array s from index l
to index u is:
(* slice : a array * int * int -> a array *)
fun slice(s,l,u) = let
fun f i = sub(s,i+l)
in
tabulate(u-l+1,f)
end ;
Answer to 5.4: Here is a complete C program that simulates the card game.
Please note that the function player_A does not read secret, but only passes
it on to player_B.
#include <stdlib.h>
#define n_number 4
#define n_card 3
typedef int deck[n_card][n_number] ;
Revision: 6.37
354 Appendix A. Answers to exercises
Answer to 5.6: In all other cases, the programmer can argue that the index is
within range. During the initialisation of hist the integer i runs from 0 to u-l,
hence it is within the bounds. The index i in input[ i ] is running from 0 to
some number, but as long as the NULL character has not been seen it is within the
bounds. Similarly, the variable i is in the range 0 . . . u-l-1 when printing the
histogram.
Note that if it was known a priori that all characters in the input array
are always in the range a ... z , then the bound check before updating
input[ i ] could be removed.
Revision: 6.37
355
Answer to 5.10:
(a) The function print_three prints the first three characters of its (string) ar-
gument. Strings are conventionally terminated by a null character. If the
string contains fewer than three characters this can be detected.
void print_three( char s [] ) {
int i ;
for( i = 0; i < 3; i++ ) {
if( s[i] == \0 ) {
return ;
} else {
printf( "%c", s[i] ) ;
}
}
}
(b) The data structure month below has two fields: one to store the number of
days in a month and the other to store the name of the month. Information
from the application domain is used to limit the size of the strings used to
one more than the maximum number of characters in a month (1+9).
typedef struct {
int days ;
char name[10] ;
Revision: 6.37
356 Appendix A. Answers to exercises
} month ;
The array leap as declared below has 12 entries because there are 12 months
in a year:
month leap [12] = { {31, "January"},
{29, "February"},
{31, "March"},
{30, "April"},
{31, "May"},
{30, "June"},
{31, "July"},
{31, "August"},
{30, "September"},
{31, "October"},
{30, "November"},
{31, "December"} } ;
(c) The function print_year below prints a table of the name and the number
of days for each month of a year.
void print_year( month a_year [] ) {
int i ;
int total_days = 0 ;
for( i = 0; i < 12; i++) {
total_days += a_year[i].days ;
print_three( a_year[i].name ) ;
printf( ". has %d days\n", a_year[i].days ) ;
}
printf( "This year has %d days\n", total_days ) ;
}
Answer to 5.11: Here is a complete C program that includes both a discrete ver-
sion of extra_bisection and a main program to test it.
#include <stdlib.h>
#include <string.h>
Revision: 6.37
357
if( f_m == 0 ) {
return m ;
} else if( h-l <= 1 ) {
return m ;
} else if( f_m < 0 ) {
l = m ;
} else {
h = m ;
}
}
}
Answer to 5.12:
int findbreak( void *p, int i ) {
if( brk( i ) == 0 ) {
return -1 ;
}
return 1 ;
}
Answer to 5.13:
(a) Here are two appropriate constants and a typedef suitable to store the
exam results.
#define max_student 130
#define max_module 12
Revision: 6.37
358 Appendix A. Answers to exercises
(c) Here is a function print to display the exam results nicely formatted:
void print( exam table ) {
int s, m ;
bool ok = true ;
for( s = 0; s < max_student; s++ ) {
int n = non_zero( table, s ) ;
if( n > 0 ) {
int t = total( table, s ) ;
printf( "%4d:", s ) ;
for( m = 0; m < max_module; m++ ) {
printf( "%3d", table[s][m] ) ;
if( table[s][m] < 0 || table[s][m] > 100 ) {
printf( "?" ) ;
ok = false ;
} else {
printf( " " ) ;
}
}
printf( "%4d %4d %6.2f\n", t, n, (double) t /
(double) n ) ;
}
}
printf( "exam results " ) ;
if( ok ) {
printf( "ok\n" ) ;
} else {
printf( "not ok\n" ) ;
}
}
The print function uses an auxiliary function:
int non_zero( exam table, int student ) {
int m ;
Revision: 6.37
359
int n = 0 ;
for( m = 0; m < max_module; m++ ) {
if( table[student][m] != 0 ) {
n ++ ;
}
}
return n ;
}
(d) Here is a main program that initialises the table to some arbitrary values and
then prints it.
int main( void ) {
{ exam table = {{1,2,3},{4,5,6,7},{0},{8},{-3}} ;
print( table ) ;
}
{ exam table = {{1,2,3},{4,5,6,7},{0},{8},{3}} ;
print( table ) ;
}
return 0 ;
}
(e) Code has been included to check that when printing the table, each score
is within the permitted range. The program prints ”exam results ok” if all
scores are within range and it prints ”exam results not ok” otherwise. Each
offending score is also accompanied by a question mark.
Answer to 5.14: Here are the C data structures required to support the spread
sheet.
typedef struct {
int the_day ;
int the_month ;
int the_year ;
} date ;
typedef struct {
int the_hour ;
int the_minute ;
Revision: 6.37
360 Appendix A. Answers to exercises
} time ;
(c) A work sheet is a collection of cells with their identification:
#define max_row 10
#define max_column 10
typedef struct {
name the_name ;
date the_date ;
time the_time ;
cell the_cell[max_row][max_column] ;
} sheet ;
(d) Here is the type of all possible cell values:
#define max_formula 80
typedef char formula[max_formula] ;
typedef union {
formula the_formula ;
int the_int ;
float the_real ;
bool the_bool ;
} value ;
(e) Here are the four different cell kinds:
typedef enum { Formula, Int, Real, Bool } kind ;
(f) A cell has a flag to tell whether it is in use:
typedef struct {
bool in_use ;
kind the_kind ;
value the_value ;
} cell ;
.
✂
✠ ✝
#include <stdio.h>
#define maxmax 17
typedef int magic[maxmax][maxmax] ;
Revision: 6.37
361
Revision: 6.37
362 Appendix A. Answers to exercises
Answer to 6.2:
typedef enum { Cons, Nil } char_list_tags ;
Revision: 6.34
363
Answer to 6.4: The C implementation of nth shown below aborts with an error
message when a non-existent list element is accessed. It is the result of using the
general while-schema of Chapter 3.
char nth( char_list x_xs, int n ) {
while( true ) {
if( x_xs == NULL ) {
printf( "nth\n" ) ;
abort() ;
}
if ( n == 0 ) {
return head( x_xs ) ;
}
x_xs = tail( x_xs ) ;
n-- ;
}
}
Revision: 6.34
364 Appendix A. Answers to exercises
Answer to 6.6: Filtering a sequence to select only the elements that satisfy a ✄
✁ ✍ ✟ ✁ ✡ ✁ ✟ ✢ ✁ ✟ ✁ ✁ ✟ ✟ ✟
filter ☞ ✄ squash ✗ ✄ ✛ domain ✄ ✬ ☞ ✄ ✯
The set comprehension above does not produce a sequence, but a mere set of
maplets. It is not a sequence because there may be ‘holes’ in the domain. The
auxiliary function ‘squashes’ the domain so that all the holes are removed. Here is
the definition of squash:
✁✄✂ ☎ ✡ ✟☛✡ ✁✳✂ ☎ ✡ ✟
squash ✁ ✁
✁ ✟ ✁ ✟ ✡ ✁ ✟ ✢ ✁ ✟ ✁ ✁ ✟ ✟
squash ✄ ✗ ✄ ✦✛ domain ✄ ✬
✟
✗✚✪ ✏✒✏✒✏ ✁
✯ domain ✄ ✯
✁ ✍ ✟ ✡ ✁ ✁ ✟ ✟ ✢ ✁ ✟
map ✄ ✗ ✄ ✛ domain ✄ ✯
Answer to 6.10: A version of copy that traverses the input list from the right,
using foldr, is:
(* copy : char_list -> char_list *)
fun copy xs = let
val n = length xs
fun copy i accu = Cons(nth xs i,accu)
in
foldr copy Nil (0 -- n-1)
end ;
Revision: 6.34
365
The for-schema can be used to translate this function into C. After simplification
this yields:
char_list copy( char_list xs ) {
int i ;
int n = length( xs ) ;
char_list accu = NULL ;
for( i = n-1; i >= 0; i-- ) {
accu = cons( nth( xs, i ), accu ) ;
}
return accu ;
}
Answer to 6.11: Using the similarity between copy and append, the following
programming can be derived straightforwardly:
char_list append( char_list xs, char_list ys ) {
char_list accu = ys ;
char_list *last = &accu ;
while( xs != NULL ) {
char_list new = cons( head( xs ), ys ) ;
*last = new ;
last = &new->list_tail ;
xs = tail( xs ) ;
}
return accu ;
}
Answer to 6.12: Using the similarity between map and copy, the following
function can be derived:
char_list map( char (*f)( char ), char_list xs ) {
char_list accu = NULL ;
char_list *last = &accu ;
while( xs != NULL ) {
char_list new = cons( f( head( xs ) ), NULL ) ;
*last = new ;
last = &new->list_tail ;
xs = tail( xs ) ;
}
return accu ;
}
Note that by using the advanced pointer technique the resulting function is
shorter.
Revision: 6.34
366 Appendix A. Answers to exercises
Answer to 6.13: Using the similarity between copy and filter, the following
program can be derived:
char_list filter( bool (*pred)( char ), char_list xs ) {
char_list accu = NULL ;
char_list *last = &accu ;
while( xs != NULL ) {
const char x = head( xs ) ;
if( pred( x ) ) {
char_list new = cons( x, NULL ) ;
*last = new ;
last = &new->list_tail ;
}
xs = tail( xs ) ;
}
return accu ;
}
Answer to 6.14: The recursive function that transfers the elements of an array
into a list follows. It is inefficient, since it needs an amount of stack space propor-
tional to the length of the array. The solution uses an auxiliary function traverse
to traverse the index range l . . . u.
(* array_to_list : char array -> char_list *)
fun array_to_list s
= let
val l = 0
val u = length s - 1
fun traverse s i u
= if i < u
then Cons(sub(s, i), traverse s (i+1) u)
else Nil
in
traverse s l u
end ;
The translation of traverse to C is an application of the multiple argument
while-schema:
char_list traverse( char s[], int i, int u ) {
if( i < u ) {
return cons( s[i], traverse( s, i+1, u ) ) ;
} else {
return NULL ;
}
}
Revision: 6.34
367
✁ ✟ ✡ ✁ ✟ ✢ ✁ ✟
reverse ✄ ✄ ✄ ✞ domain ✄
✟
✗ ☛✛ ✯
Answer to 6.19:
(a) Here are the C #define and typedef declarations that describe a binary
tree with integers at the leaves:
typedef enum {Branch, Leaf} tree_tag ;
Revision: 6.34
368 Appendix A. Answers to exercises
Revision: 6.34
369
Answer to 6.20:
(a) The type tree_ptr defines a pointer to a structure that can either accommo-
date a binding or a node of a tree. Each tree node contains a key field that
will correspond to the constructor in SML, and it contains a (pointer to an)
array with sub trees. The number of sub trees is stored in the size-member.
(b) The functions mkBind and mkData allocate a tree_struct. Both functions
print out the data such that the execution of the functions can be traced. The
pointers returned by malloc should be checked for a NULL value.
tree_ptr mkBind( tree_ptr * b ) {
tree_ptr tree ;
tree = malloc( sizeof( struct tree_struct ) ) ;
tree->tag = Bind ;
tree->alt.bind = b ;
printf( "mkBind( %p ): %p\n", b, tree ) ;
return tree ;
}
Revision: 6.34
370 Appendix A. Answers to exercises
tree->alt.comp.data[i] = d ;
printf( ", %p", d ) ;
}
va_end( ap ) ;
printf( " ): %p\n", tree ) ;
return tree ;
}
The %p format prints a pointer.
(c) The function match performs case analysis on the argument pat. If it is a
binding, the value of exp is stored in the appropriate location. If we are deal-
ing with Data, sub trees will be matched, provided that the key and size of
the pattern and expression agree.
bool match( tree_ptr pat, tree_ptr exp ) {
switch( pat->tag ) {
case Bind :
* (pat->alt.bind) = exp ;
return true ;
case Data :
if ( exp->tag == Data &&
exp->alt.comp.key == pat->alt.comp.key &&
exp->alt.comp.size == pat->alt.comp.size ) {
int i ;
for( i = 0; i < pat->alt.comp.size; i++ ) {
if( ! match( pat->alt.comp.data[i],
exp->alt.comp.data[i] ) ) {
return false ;
}
}
return true ;
} else {
return false ;
}
}
abort() ;
}
(d) The main function below creates a pattern and an expression. It then tries to
match both, which will succeed. This binds the sub tree with keyB to the
variable b, the sub treeC to c and the sub treeD to d. The second call
to match fails, because the keys and sizes of pat and b disagree. The third
call to match also fails, because the keys of pat and c disagree, even though
their sizes do agree. The last call to match succeeds.
int main( void ) {
tree_ptr a, b, c, d ;
tree_ptr exp = mkData( A, 3,
Revision: 6.34
371
mkData( B, 0 ),
mkData( C, 0 ),
mkData( D, 3,
mkData( E, 0 ),
mkData( F, 0 ),
mkData( G, 0 ) ) ) ;
tree_ptr pat = mkData( A, 3,
mkBind( &b ),
mkBind( &c ),
mkBind( &d ) ) ;
if( match( pat, exp ) ) {
printf( "1: b=%p, c=%p, d=%p\n", b, c, d ) ;
}
if( match( pat, b ) ) {
printf( "2: b=%p, c=%p, d=%p\n", b, c, d ) ;
}
if( match( pat, c ) ) {
printf( "3: b=%p, c=%p, d=%p\n", b, c, d ) ;
}
if( match( mkBind( &a ), exp ) ) {
printf( "4: a=%p\n", a ) ;
}
return 0 ;
}
Revision: 6.34
372 Appendix A. Answers to exercises
Revision: 6.33
373
Revision: 6.33
374 Appendix A. Answers to exercises
Answer to 7.10: Here is a main program to initialise the queue and count the
number of occurrences of some word in a text.
int main( void ) {
char_list word = array_to_list( "cucumber", 8 ) ;
char_queue text = create( stdin, length( word ) ) ;
printf( "cucumber: %d times\n", word_count( word, text ) ) ;
return 0 ;
}
Answer to 7.11: The function valid returns the number of valid elements in
the array based queue.
(* valid : a queue -> int *)
fun valid (Queue (stream, valid, array)) = valid ;
In C, this becomes:
int valid( char_queue q ) {
return q->queue_valid ;
}
Revision: 6.33
375
Answer to 7.12: Here are the SML and C versions of word_count and match
using arrays.
(* list_match : char list -> char list -> bool *)
fun list_match [] t = true
| list_match (w::ws) [] = false
| list_match (w::ws) (t::ts) = w = (t:char) andalso
list_match ws ts ;
Answer to 7.14: We need an auxiliary function to free a list; this function has
been defined in Chapter 6.
Revision: 6.33
376 Appendix A. Answers to exercises
Revision: 6.33
377
times this can be improved by defining functions in such a way that they do de-
stroy their arguments. As an example, we could split the list p_xs destructively
into pivot, less, and more, and append could destructively append its argu-
ments. In that case, qsort would destroy its argument, and one can write a quick-
sort that does not have to allocate or free any cells. In order to create a functional
interface to the function, one needs an auxiliary function quicksort that first
copies its argument before calling the destructive qsort.
Answer to 7.15: Here is an SML function main to call qsort on the data:
(* main : char list *)
val main = qsort (explode "ECFBACG") ;
Here is the corresponding C version:
int main( void ) {
char_list sorted = qsort( array_to_list( "ECFBACG", 7 ) ) ;
list_to_stream( stdout, sorted ) ;
putchar( \n ) ;
return 0 ;
}
Answer to 7.16:
void qsort( char data [], int l, int r ) {
if( l < r) {
int p = l ;
char data_p = data[p] ;
int i = l ;
int j = r ;
while( true ) {
i = up( data_p, data, i, r ) ;
j = down( data_p, data, l, j ) ;
if( i < j ) {
swap( data, i, j ) ;
i++ ;
j-- ;
} else if( i < p ) {
swap( data, i, p ) ;
i++ ;
break ;
} else if( p < j ) {
swap( data, p, j ) ;
j-- ;
break ;
} else {
break ;
Revision: 6.33
378 Appendix A. Answers to exercises
}
}
qsort( data, l, j ) ;
qsort( data, i, r ) ;
}
}
Revision: 6.33
379
#include <stdio.h>
#include <stdlib.h>
Revision: 6.33
380 Appendix A. Answers to exercises
cc -c graphics.c graphics.c
graphics.o
graphics.h
cc -c vector.c vector.c
vector.o
vector.h
cc -c matrix.c matrix.c
matrix.o
matrix.h
Answer to 8.3: Following the arrows shows that graphics.o, matrix.o, and
graphics need to be remade if matrix.h is changed.
Revision: 6.38
381
cc -c graphics.c graphics.c
graphics.o
graphics.h
cc -c matrix.c matrix.c
matrix.o
matrix.h
Answer to 8.9: The polymorphic open list version of append with advanced use
of pointers is:
list append( list xs, list ys, int size ) {
list accu = ys ;
list *lastptr = &accu ;
while( xs != NULL ) {
list new = cons( head( xs ), ys, size ) ;
Revision: 6.38
382 Appendix A. Answers to exercises
*lastptr = new ;
lastptr = & new->list_tail ;
xs = tail( xs ) ;
}
return accu ;
}
Answer to 8.11: This answer only gives the header file for the polymorphic array
ADT:
typedef struct Array *Array ;
Answer to 8.13:
Revision: 6.38
383
(a) Here are the functions snoc, head and tail that operate on a snoc list:
snoc_list snoc( snoc_list head, void * tail ) {
snoc_list l = malloc( sizeof( struct snoc_struct ) ) ;
if( l == NULL ) {
printf( "snoc: no space\n" ) ;
abort( ) ;
}
l->snoc_head = head ;
l->snoc_tail = tail ;
return l ;
}
Revision: 6.38
384 Appendix A. Answers to exercises
Answer to 8.14:
Revision: 6.38
385
case Lf :
printf( "%d", t->alt.lf ) ;
break ;
case Br :
printf( "(" ) ;
sprint( print_elem, t->alt.br ) ;
printf( ")" ) ;
break ;
}
}
(d) Here is a main program to test the ntree data types and associated func-
tions:
int main( void ) {
snoc_list l2 = snoc( snoc( snoc( NULL,
nlf( 2 )
),
nlf( 3 )
),
nlf( 4 )
) ;
snoc_list l1 = snoc( snoc( NULL,
nlf( 1 )
),
nbr( l2 )
);
ntree t1 = nbr( l1 ) ;
ntree t2 = nbr( NULL ) ;
nprint( t1 ) ;
printf( "\n" ) ;
nprint( t2 ) ;
printf( "\n" ) ;
return 0 ;
}
Answer to 8.15:
(a) Here is the C interface snoclist.h for the snoc list module:
#ifndef SNOC_LIST_H
#define SNOC_LIST_H
typedef struct snoc_struct * snoc_list ;
Revision: 6.38
386 Appendix A. Answers to exercises
struct snoc_struct {
void * snoc_tail ;
struct snoc_struct * snoc_head ;
} ;
snoc_list snoc( snoc_list head, void * tail ) {
snoc_list l = malloc( sizeof( struct snoc_struct ) ) ;
if( l == NULL ) {
printf( "snoc: no space\n" ) ;
abort( ) ;
}
l->snoc_head = head ;
l->snoc_tail = tail ;
return l ;
}
snoc_list head( snoc_list l ) {
return l->snoc_head ;
}
Revision: 6.38
387
struct ntree_struct {
ntree_tag tag ;
union {
int lf ; /* tag Lf */
snoc_list br ; /* tag Br */
} alt ;
} ;
ntree nlf( int lf ) {
ntree t ;
t = malloc( sizeof( struct ntree_struct ) ) ;
if( t == NULL ) {
printf( "nlf: no space\n" ) ;
abort( ) ;
}
t->tag = Lf ;
t->alt.lf = lf ;
return t ;
}
ntree nbr( snoc_list br ) {
ntree t ;
t = malloc( sizeof( struct ntree_struct ) ) ;
if( t == NULL ) {
printf( "nbr: no space\n" ) ;
abort( ) ;
}
t->tag = Br ;
t->alt.br = br ;
return t ;
}
void nprint( ntree t ) ;
Revision: 6.38
388 Appendix A. Answers to exercises
Revision: 6.38
389
Answer to 9.4: The SML data structure uses a type variablea . This type is re-
turned by the Open function and used by subsequent functions. The C data struc-
ture uses void * as replacement for polymorphic typing. The C compiler cannot
check whether the appropriate data structure is passed from the open-function to
the draw-functions (see Section 4.5).
Answer to 9.6: The PostScript driver for the device independent graphics is:
#include "PSdriver.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct {
FILE *file ;
} PSInfo ;
Revision: 1.25
390 Appendix A. Answers to exercises
void PS_draw_line(void *g, int x0, int y0, int x1, int y1) {
PSInfo *i = g ;
fprintf( i->file, "newpath\n" ) ;
fprintf( i->file, "%d %d moveto\n", x0, y0 ) ;
fprintf( i->file, "%d %d lineto\n", x1, y1 ) ;
fprintf( i->file, "closepath\n" ) ;
fprintf( i->file, "stroke\n" ) ;
}
void PS_draw_box(void *g, int x0, int y0, int x1, int y1) {
PSInfo *i = g ;
fprintf( i->file, "newpath\n" ) ;
fprintf( i->file, "%d %d moveto\n", x0, y0 ) ;
fprintf( i->file, "%d %d lineto\n", x0, y1 ) ;
fprintf( i->file, "%d %d lineto\n", x1, y1 ) ;
fprintf( i->file, "%d %d lineto\n", x1, y0 ) ;
fprintf( i->file, "%d %d lineto\n", x0, y0 ) ;
fprintf( i->file, "closepath\n" ) ;
fprintf( i->file, "stroke\n" ) ;
}
graphics_driver PSDriver = {
PS_open,
PS_draw_line,
PS_draw_box,
/*C other functions of the driver*/
PS_close
} ;
Revision: 1.25
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Appendix B
In this appendix we will present a brief overview of the salient features of SML
for the reader who is familiar with another functional programming language. We
cover just enough material to make the reader feel comfortable when reading our
book. We should point out that SML has more to offer than we use in this book;
the interested reader might wish to consult one of the many textbooks available on
programming in SML [15, 9, 16].
(or operands) of different types. For example, in the expression , the operands
✂ ✝
✏ ✪ ✂ ✏
391
392 Appendix B. A brief review of SML
The main difference between Lisp and SML on the one hand and Miranda and
Haskell on the other hand is that Lisp and SML are eager languages whereas Mi-
randa and Haskell are lazy languages. In an eager language, the argument to a
function is always evaluated before the function is called. In a lazy language, the
evaluation of an argument to a function may be postponed until the evaluation of
the argument is necessary. If the function does not actually use its argument, it
will never be evaluated, thus saving some execution time. The functions that we
use in this book work in an eager language. It would thus be possible to interpret
all our SML functions in Haskell or Miranda.
The module systems of the four programming languages are all rather differ-
ent. SML has the most sophisticated module system. The module systems of
Haskell and Miranda are simpler but effective. Many different Lisp systems ex-
ists, with a wide variation of module systems, ranging from the primitive to the
sophisticated.
Below we discuss the basic elements of a functional program, and describe the
particular features offered by SML. In its basic form, a module from a functional
program consists of a number of data type and function definitions, and an ex-
pression to be evaluated.
✂✁
✍ ☞ ☞ ✎
✝✞
if
✁ ✁
✁
✎ ✎
✞✠
☞ ✍
✂
☞
otherwise
Here is the SML function over which uses this equation. The type of the function
is given as a comment. It states that over has two integer arguments and that its
function result is also an integer.
(* over : int -> int -> int *)
fun over n 0 = 1
| over n m = if n = m
then 1
else over (n-1) m + over (n-1) (m-1) ;
A function definition in SML is introduced by the keyword fun and terminated by
a semicolon ;. Layout is not significant in SML; in particular, there is no off side
rule as in Miranda and Haskell. The function name is over. The function name
is then followed by the names of the formal arguments. The function over has
two alternative definitions, separated by the vertical bar |. The first clause applies
when the second argument has the value 0. The second clause applies when the
Revision: 6.18
B.2. Functions, pattern matching, and integers 393
second argument, m, is not equal to 0. The two defining clauses of the function
are distinguished by pattern matching on the argument(s). Clauses are tried top
down, and arguments are matched left to right.
The function result of the first clause of over is 1; that of the second clause is
determined by the conditional expression if . . . then . . . else . . . . If the values
of n and m are equal, the function result is also 1. If n and m are unequal, two
recursive calls are made and the results are added.
In most functional languages, parentheses serve to build expressions out of
groups of symbols and not to delineate function arguments. This explains why
the recursive call to the function over looks like this:
over (n-1) (m-1)
One might have expected it to look like this:
over(n-1,m-1)
This latter notation would be more consistent with common mathematical use of
parentheses to delineate function arguments. In SML, but also in Haskell and Mi-
randa, it is possible to enclose functional arguments in parentheses and to sepa-
rate the arguments by commas. With this notation, we can define a new function
over to look like this (an identifier may contain an apostrophe as a legal char-
acter):
(* over : int * int -> int *)
fun over(n,0) = 1
| over(n,m) = if n = m
then 1
else over(n-1,m) + over(n-1,m-1) ;
The two arguments n and m are now treated as a tuple consisting of two integers.
This is indicated in the type of the function, which indicates that it expects one
argument of type (int * int). This notation is called the uncurried notation. In
the curried notation a function takes it arguments one by one. The curried notation
is commonly used in lazy languages such as Haskell or Miranda programs. It is
easy to mix the two notations inadvertently, but the compilers will discover such
errors because of the ensuing type mismatches.
Haskell and Miranda do not distinguish between functions with arguments
and functions without arguments. However, SML does distinguish between the
two. A function without arguments is a value, which may be given a name using
the val declaration. The following defines two values, over_4_3 and over_4_2.
Both are of type int:
(* over_4_3,over_4_2 : int *)
val over_4_3 = over 4 3 ;
val over_4_2 = over 4 2 ;
The results printed by the SML system are 4 and 6 respectively.
Revision: 6.18
394 Appendix B. A brief review of SML
Revision: 6.18
B.5. Type synonyms and algebraic data types 395
pattern symbol for lists. In Haskell and Miranda : is the pattern symbol for lists
and :: introduces a type.
The operator orelse in SML is the logical disjunction. It evaluates its left
operand first. If this yields true, then it does not evaluate the right operand. If
the leftmost operand evaluates to false, then the rightmost operand of orelse
is evaluated. The orelse operator is said to have short-circuit semantics. The
orelse operator has two companions: the andalso operator for the logical con-
junction and the conditional construct if . . . then . . . else. All three have short-
circuit semantics. These are the only exceptions to the rule that SML functions and
operators always evaluate their arguments first.
The function locate is polymorphic in the type of the element to be looked
up in the list. Therefore the following are all valid uses of locate:
(* locate_nil,locate_bool,locate_int : bool *)
(* locate_char,locate_string,locate_real : bool *)
val locate_nil = locate 2 [] ;
val locate_bool = locate true [false,false] ;
val locate_int = locate 2 [1,2,3] ;
val locate_char = locate "x" ["a","b","c","d"] ;
val locate_string = locate "foo" ["foo","bar"] ;
val locate_real = locate 3.14 [0.0, 6.02E23] ;
The notation [1,2,3] is shorthand for 1::(2::(3::[])), where [] is the
empty list and :: is the concatenation of a new element to the head of a list.
SML does not offer characters as a basic type. Instead, it provides character
strings enclosed in double quotes ". By convention, a string with a single char-
acter is used where one would use a character in Haskell or Miranda. A further
property of SML is that a string is not a list of characters but a separate data type.
The functions explode and implode convert between an SML string and a list of
single element strings. The two equations below are both true:
(* true_explode,true_implode : bool *)
val true_explode = (explode "foo" = ["f","o","o"]) ;
val true_implode = (implode ["b","a","r"] = "bar") ;
Revision: 6.18
396 Appendix B. A brief review of SML
New types can be created using a algebraic data type declaration. Here is how
a binary tree would be defined with integers at the leaf nodes:
datatype int_tree = Int_Branch of int_tree * int_tree
| Int_Leaf of int ;
The keyword datatype announces the declaration of an algebraic data type.
Here the identifier Int_Branch is a data constructor, which can be viewed as a
function. This function should be applied to a tuple consisting of a left subtree
and a right subtree. Both subtrees are values of type int_tree. The identifier
Int_Leaf is again a data constructor, this time taking an integer value as an ar-
gument. SML does not permit curried constructors; Haskell and Miranda allow
both curried and uncurried constructors but the former are more common. Here
is the usual curried Miranda notation:
num_tree ::= Num_Branch num_tree num_tree |
Num_Leaf num ;
Let us now use the int_tree data type definition to create a sample trees in SML:
(* sample_int_tree : int_tree *)
val sample_int_tree
= Int_Branch(Int_Leaf 1,
Int_Branch(Int_Leaf 2,Int_Leaf 3)) ;
A graphical representation of this tree would be as follows:
2 3
The function walk_add, as given below, traverses a tree, adding the numbers
stored in the leaves. The function definition uses pattern matching on the con-
structors of the data structure:
(* walk_add : int_tree -> int *)
fun walk_add (Int_Branch(left,right))
= walk_add left + walk_add right
| walk_add (Int_Leaf data)
= data ;
The type of walk_add states that the function has one argument with values of
type int_tree and that the function result is of the type int. The definition of
walk_add has two clauses. The first clause applies when an interior node is en-
countered. As interior nodes do not contain data, the function result returned by
this clause is the sum of the results from the left and right branch. The function
result returned by the second clause is the data stored in a leaf node.
Revision: 6.18
B.6. Higher order functions 397
Revision: 6.18
398 Appendix B. A brief review of SML
= let
fun add x y = x+y
in
poly_walk add sample_poly_tree
end ;
The following identity relates the two tree walk functions that have been defined
thus far:
poly_walk add a_tree walk_add a_tree
It is possible to prove that this equality holds for all finite trees by induction on the
structure of the argument a_tree.
Here is another tree built using the tree data type. This time, we have strings
at the leaves:
(* string_poly_tree : string tree *)
val string_poly_tree
= Branch(Leaf "Good",
Branch(Leaf "Bad",Leaf "Ugly")) ;
A graphical representation of this tree would be:
”Good”
”Bad” ”Ugly”
A function suitable to traverse this tree would be one that concatenates the strings
found at the leaves. To make the output look pretty it also inserts a space between
the words found in the tree:
(* film : string *)
val film
= let
fun concat x y = x ˆ " " ˆ y
in
poly_walk concat string_poly_tree
end ;
The string value of film is thus "Good Bad Ugly". Polymorphism has helped
to produce two different functions with code reuse.
B.7 Modules
A module system serves to gather related type, data type, function, and value dec-
larations together so that the collection of these items can be stored, used, and
Revision: 6.18
B.7. Modules 399
manipulated as a unit. The module system of SML is one of the most advanced
available to date in any programming language. We will discuss here the basics
of the module system. The most striking feature of the module system is its sys-
tematic design. An SML structure is a collection of types, data types, functions,
and values. A signature basically gives just the types of these items in a structure.
The relation between structures and signatures is roughly the same as the relation
between types on the one hand and functions and values on the other. Put differ-
ently, signatures (types) are an abstraction of structures (values and functions).
Consider the following structure as an example. It collects an extended poly-
morphic tree data type tree, an empty tree empty, and an extended tree walk
function walk into a structure called Tree. (The extensions were made to create a
more interesting example, not because of limitations of the module mechanism.)
structure Tree = struct
(* empty : a tree *)
val empty = Empty ;
end ;
The keyword structure announces that the declaration of a structure follows.
This is similar to the use of the keywords fun and val. The keyword struct
is paired with the keyword end. These two keywords delineate the declaration
of the three components of the structure. The declarations of the components of
the structure (the data type tree, the value empty, and the function walk) are
created according to the normal rules for declaring such items.
With the structure declaration in place, we can create a sample tree by qualify-
ing each identifier from the structure by the name of the structure. This time, we
will create a tree of reals as an example:
(* sample_real_tree : real Tree.tree *)
val sample_real_tree
= Tree.Branch(Tree.Leaf 1.0,
Tree.Branch(Tree.Leaf 2.0,Tree.Leaf 3.0));
It is necessary to indicate from which structure a particular component emerges,
Revision: 6.18
400 Appendix B. A brief review of SML
Revision: 6.18
B.8. Libraries 401
value empty. This effectively restricts the use that can be made of components
of a structure. Such restrictions are useful to prevent auxiliary types, data types,
functions, and values from being used outside the structure.
signature NON_EMPTY_TREE = sig
datatype a tree = Branch of a tree * a tree
| Leaf of a
| Empty ;
val walk : (a->a->a) -> a -> a tree -> a
end ;
B.8 Libraries
The SML language provides a set of predefined operators and functions. Fur-
thermore, different implementations of SML may each offer an extensive set of li-
braries. We have made as little use as possible of the wealth of library functions
that are available to the SML programmer. Firstly, having to know about a min-
imal core of SML and its libraries makes it easier to concentrate on learning C.
Secondly, by using only a few library functions the book is only loosely tied to a
particular implementation of SML.
We use a small number of predefined operators and functions and only four
functions from the SML/NJ array library. In addition, we have defined a number
of functions of our own which are similar to the SML library functions of most
implementations.
Here are the predefined operators and their types as they are being used
throughout the book:
operator type
+, -, *, div, mod int * int -> int
+, -, *, / real * real -> real
<, <=, <>, =, >=, > int * int -> bool
<, <=, <>, =, >=, > real * real -> bool
ˆ string * string -> string
size string -> int
ord string -> int
chr int -> string
:: a * a list -> a list
@ a list * a list -> a list
The list processing functions below are similar to those found in Haskell and Mi-
randa. The SML versions that we use are not from a standard library. Instead, they
Revision: 6.18
402 Appendix B. A brief review of SML
have been defined in the text and are used in many places. Several of these func-
tions are used in a specific monomorphic context. We only give the polymorphic
forms here. The name and type of each function is accompanied by the number
of the page where the function is defined and described. The list is in alphabetical
order.
function type page number
(--) int * int -> int list 80
append a list -> a list -> a list 186
filter (a -> bool) -> a list -> a list 92
foldl (a -> b -> a) -> a -> b list -> a 80
foldr (b -> a -> a) -> a -> b list -> a 86
head a list -> a 183
length a list -> int 185
map (a -> b) -> a list -> b list 89
nth a list -> int -> a 186
prod int list -> int 81
sum int list -> int 89
tail a list -> a list 183
Here are the four array functions that we use from the SML/NJ array module.
They are similar to the array processing functions from Haskell.
We add to this repertoire of basic array functions three further generally useful
functions:
function type page number
concatenate a array * a array -> a array 138
slice a array * int * int -> a array 138
upd a array * int * a -> a array 137
This concludes the presentation of the relatively small core of SML that we use in
the book to study programming in C.
Revision: 6.8
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Appendix C
Standard Libraries
C offers a rich variety of library functions. Some of these functions are used so
often that a short reference is indispensable to the reader of this book. This chapter
provides such a reference. For the complete set of standard libraries one has to
consult the C reference manual [7].
Besides the standard libraries many system calls are usually directly available to
the C programmer. As an example, functions for accessing files under UNIX, Win-
dows, or Macintosh and usually also functions to manage processes or network
connections are readily available.
In addition to these, there are many other libraries available. We have seen a
small part of the X-window library in Chapter 9. Other libraries that exist are for
example cryptographic libraries [11], and numerical libraries [10]. For all these, we
refer the reader to the appropriate documentation.
The standard library consists of a collection of modules. Each of these modules
requires a different interface file to be loaded. Most computer systems will not
require you to specify that you wish to link any of the libraries, with the excep-
tion of the mathematics library under UNIX. Below, five modules are explained in
detail: I/O, strings, character types, mathematics, and utilities. The final section
summarises the purpose of the modules that we have not described.
403
404 Appendix C. Standard Libraries
FILE Is a type that stores the information related to a file. Normally only refer-
ences to this types are passed, which are thus of type FILE *. An entity of
type FILE * is referred to as a file pointer or a stream.
FILE *stdin Is a global identifier referring to the standard input stream.
FILE *stdout Is a global identifier referring to the standard output stream.
FILE *stderr Is a global identifier referring to the standard error stream.
void putchar( char c ) Puts a character on the standard output stream.
void printf( char *format, ... ) Prints its arguments on the standard
output stream. The first argument is the format string, which specifies how
the other parameters are printed. The format string is copied onto the out-
put, with the exception of % specifiers. Whenever a % is encountered, one of
the arguments is formatted and printed. For example, a %d indicates an in-
teger argument, %f a floating point number, %s a string, %c a character, and
%p a pointer.
int getchar( void ) Reads one character from the standard input. It returns
this character as an integer, or, if there are no more characters, the special
value EOF (End Of File). Note that EOF is an integer which cannot be repre-
sented as a character.
int scanf( char *format, ... ) Reads values from the standard input
stream. The first argument is a string that specifies what types of values to
read. The format of the string is similar to that of printf: %d specifies read-
ing an integer, %f a floating point number, and so on. The subsequent pa-
rameters must be pointers to variables with the appropriate type: int for %d,
char [] for %s, and so on. Types are not checked; therefore, accidentally
forgetting an & may have disastrous results. It is particularly important to
notice the difference between the formats %f and %lf. The first one expects
a pointer to a float, the second expects a pointer to a double. You will
probably need the latter one.
The function scanf will read through the input stream, matching the input
with the formats specified. When a format is successfully matched, the re-
sulting value is stored via the associated pointer. If a match fails, then scanf
will give up and return, leaving the input positioned at the first unrecog-
nised character. The return value of scanf equals the number of items that
were matched and stored. When the input stream is empty, EOF will be re-
turned.
FILE *fopen( char *filename, char *mode ) Creates a file descriptor
that is associated with a file of your file system. The first argument speci-
fies the filename, the second the mode. Two frequently used modes are "r"
(opens the file for reading, you can use it with functions like scanf) and "w"
(opens the file for writing). If the file cannot be opened, a NULL pointer is re-
turned.
void fprintf( FILE *out, char *format, ... ) Is like printf, but
the first argument specifies on which file to print. The file can be one of
stdout, stderr, or any file opened with fopen.
void sprintf( char *out, char *format, ... ) Is like printf, but
the first argument specifies an array of characters where the output is to be
Revision: 6.8
C.2. Strings 405
stored. The array must be large enough to hold the output, no checking is
performed.
void putc( char c, FILE *out ) Is like putchar, but on a specific file.
int fscanf( FILE *in, char *format, ... ) Is like scanf, but scans
from a specific file.
int sscanf( char *in, char *format, ... ) Is like scanf, but scans
from a string.
int getc( FILE *in ) Is like getchar, but from a specific file.
An important note: the functions putchar, getchar, getc, and putc are usu-
ally implemented with macros. The macro call semantics (Section 8.1.5) can cause
strange results when the arguments of these macros have a side effect. Unexpected
results can be avoided by using fputc and fgetc instead.
C.2 Strings
A string is represented as an array of characters (see also Section 5.5). To manipu-
late these arrays of characters, a string library is provided. To use this library, the
file string.h must be included:
#include <string.h>
The most important functions of this library are:
Revision: 6.8
406 Appendix C. Standard Libraries
char *strchr( char *s, char c ) Finds the first occurrence of the char-
acter c in the string pointed to by s. A pointer to his first occurrence is re-
turned. If c does not occur in s the NULL pointer is returned.
char *strstr( char *s, char *t ) Finds the first occurrence of the
string t in the string pointed to by s. A pointer to his first occurrence is re-
turned. If t does not occur in s the NULL pointer is returned.
Apart from these operations on\0 terminated character strings, there is a series
of functions that operate on blocks of memory. These functions treat the NULL-
character as any other character. The length of the block of memory must be
passed to each of these functions.
Revision: 6.8
C.4. Mathematics 407
C.4 Mathematics
The mathematics library provides a number of general mathematical functions.
More specialised functions and numerical algorithms are provided by other li-
braries. It is essential to import the file math.h; the compiler might not warn you
if it is not included, but the functions will return random results.
#include <math.h>
The functions available in this library are:
double sin( double rad ) Calculates the sine of an angle. The angle
should be in radians.
double cos( double rad ) Calculates the cosine of an angle
double tan( double rad ) Calculates the tangent of an angle.
double asin( double x ) Calculates the arc sine of x.
double acos( double x ) Calculates the arc cosine of x.
double atan( double x ) Calculates the arc tangent of x.
double atan2( double x, double y ) Calculates the arc tangent of y/x.
(A proper result is returned when x is 0.)
double sinh( double rad ) Calculates the hyperbolic sine of x.
double cosh( double rad ) Calculates the hyperbolic cosine of x.
double tanh( double rad ) Calculates the hyperbolic tangent of x.
double exp( double x ) Calculates the exponential function of a number,
.
double log( double x ) Calculates the base (natural) logarithm of x.
double log10( double x ) Calculates the base 10 logarithm of x.
double pow( double x, double p ) Calculates x to the power p, . ✟
✁
double ceil( double x ) Calculate ✂✁ , the smallest integer not less than .
✟ ✟
The function returns a double, not an int. It does not perform a coercion,
but it only rounds a floating point number.
double floor( double x ) Calculate ✂✁ , the largest integer not greater
✟
than .✟
✢ ✢
ber.
double frexp( double x, int * n ) Splits x into a fraction
✁
and a
power of 2, , such that
☞ ✟ ✝ and
✁
Revision: 6.8
408 Appendix C. Standard Libraries
fore processing of the variable argument list begins. The variable should be
✟
the last proper argument before the ellipses (...) in the function prototype.
In the case of printf above the would be format.
✟
✁ va_arg( va_list ap, ✁ ) The va_arg macro will deliver the next item
from the argument list. This value has type . Each call to va_arg advances
✁
C.6 Miscellaneous
The utility library is a collection of miscellaneous routines that did not fit any-
where else. The utility library can be used by including stdlib.h:
#include <stdlib.h>
This module contains a large number of functions. We discuss only the most im-
portant functions below:
int abs( int x ) Returns the absolute value of an integer.
int atoi( char *string ) Converts a string to an integer (the name stands
for ascii to integer): atoi( "123" ) is 123.
double atof( char *string ) Converts a string to a floating point number.
void *calloc( int x, int y ) Allocates heap space: sufficient space is
allocated to hold x cells of size y. All space is initialised to 0. This
function returns NULL if it cannot allocate sufficient space. You can use
sizeof to find out how many bytes a certain type needs. The call
calloc( 4, sizeof( int ) ) will return a pointer to an area of store
large enough to store 4 integers.
Revision: 6.8
C.7. Other modules 409
Revision: 6.8
410 Appendix C. Standard Libraries
Revision: 6.8
c 1995,1996 Pieter Hartel & Henk Muller, all rights reserved.
Appendix D
This chapter summarises the complete syntax of ISO-C using railroad diagrams. A
railroad diagram has a name, a beginning at the top left hand side and an end at
the top right hand side. A diagram is read starting from the beginning and follow-
ing the lines and arcs to the end. Similar to what real trains on real rail roads can
do, you must always follow smooth corners and never take a sharp turn.
On your way through a rail road diagram, you will encounter various sym-
bols. There are two kinds of symbols. A symbol in a circle or an oval stands for
itself. This is called a terminal symbol. Such a symbol represents text that may be
typed as part of a syntactically correct C program. A symbol in a rectangular box
is the name of another rail road diagram. This is a non-terminal symbol. To find out
what such a symbol stands for you must lookup the corresponding diagram. Rail-
road diagrams can be recursive, when a non terminal is referring to the present
diagram.
The rail road diagrams can be used for two purposes. The first is to check that
a given C program uses the correct syntax. This should be done by starting at
the first diagram (translation˙unit), and trying to find a path through the diagrams
such that all symbols in the program are matched to symbols found on the way.
Railroad diagrams are also useful as a reminder of what the syntax exactly
looks like. A path through the diagrams corresponds to an ordering on the sym-
bols that you may use. For example if you what to know what a for-statement
looks like, you should look up the diagram called statement, find the keyword
for, and follow a path to the end of the diagram to see what you may write to
create a for-statement. Note that the railroad diagrams only describe the syntax
of the language. A syntactically correct program is not necessarily accepted by the
compiler as it may contain semantic errors (for example an illegal combination of
types).
The diagrams that represent the ISO-C syntax are ordered in a top down fash-
ion. We explain a few diagrams in some detail to help you find out for yourself
how to work with them. Let us study what a program or translation˙unit may look
✁✄???✂☎
like.
411
412 Appendix D. ISO-C syntax diagrams
✁✄???✂☎
should like to know more about each of them. Their definitions are:
✁ ???✂☎
As an example to show how the railroad diagram applies to a C constant declara-
tion, consider the following:
const double eps=0.001, delta=0.0001 ;
The words const double are matched by the diagram declaration˙specifiers (via
type˙specifier and type˙qualifier). We then take the road to the lower part of declara-
tion, where eps matches declarator, we take the road to the =, where the equal sign
matches, the constant 0.001 matches initializer. The comma brings us back at the
declarator, which matches delta, and so on.
✁ ???☎✂
✁✄???✂☎
✁ ???✂☎
The type˙qualifier diagram shows that we have not explained everything there
is to know about C, because it shows a new keyword volatile. We are not going
to explain such new features here, we should just like to point out that the syntax
given as rail road diagrams is complete. If you want to find out more bout the
keyword volatile you should consult the C reference manual [7].
Types are constructed using structures, unions and enumerations, as discussed
in Chapter 4.
✁ ???☎✂
✁ ???✂☎
✁✄???✂☎
✁ ???✂☎
Revision: 6.8
413
✁ ???☎✂
✁ ???✂☎
✁✄???✂☎
✁✄???✂☎
✁ ???✂☎
✁ ???✂☎
✁ ???✂☎
✁✄???✂☎
✁✄???✂☎
✁ ???☎✂
✁ ???✂☎
✁✄???✂☎
✁✄???✂☎
✁ ???✂☎
There are no diagrams for the symbols that represent identifiers, constants and
strings, they would not give much useful information. Instead we give an infor-
mal definition of each of these terms:
identifier A sequence composed of characters, underscores, and digits that does
not start with a digit.
Revision: 6.8
415
Revision: 6.8
416 Appendix D. ISO-C syntax diagrams
Revision: 6.8