SML - Introduction To Programming Using SML
SML - Introduction To Programming Using SML
Book reviews
refinements that are found in modern software products. They are nonetheless complete
programs. The fact that three interesting projects can be presented concisely using CML is
a convincing advertisement for the language. The window manager and Linda system are
particularly interesting; I learned a lot about those topics, and found myself thinking more
about the application than about the language.
There are two appendices, which give a reference manual for CML and present its semantics,
and an extensive bibliography.
John O’Donnell
1 Content
The overall content of the second edition – chapters, conceptual threads, . . . the story, in fact
– follows that of the first very closely, although there is extensive modification of the details
which address the few awkwardnesses of presentation in the previous version.
The first two chapters introduce the dual abstractions of procedures and data, respectively,
covering all the material that you would expect. The examples are both standard (numerical
calculations, lists and trees, etc.) as well as the less common for a first-level programming book
(symbolic differentiation, implementing generics, etc.). Of course, since a ‘proper’ language is
being used, issues such as first-class functions and recursion are introduced as easily as the
basic data structures. In addition, important topics such as complexity, and the distinctions
between names, representations and values, are developed early and naturally. In passing,
Scheme is described by introducing any new features as they are required – since there aren’t
many, this doesn’t take long, and the majority of Scheme is dealt with in Chapter 1.
An addition to the first edition is the development and use of a picture language (based
on Peter Henderson’s 1982 example) to illustrate the use of higher order functions, such as
map, as means for abstracting patterns over data.
The third chapter brings together many of the threads of the first two by developing the
idea of ‘object’. To do this it is necessary to introduce that most contentious of topics (at least
to FP aficionados): assignment. This enables the idea of state to be addressed, and hence
enables programs which model state-full entities (as exist in the ‘real’ world) to be written
in a natural way. It is worth noting that assignment, or state mutation, is regarded as a
difficult advanced topic (over 200 pages pass by before it’s introduced), that it is presented
as something to be used in a highly controlled way (mutation of local state variables using
object-style encapsulation), and that the downsides of assignment (a more complex and
complicated computational model) are firmly presented.
The additional material in Chapter 3 consists principally of a 20-page section on Con-
currency. In essence this section introduces the basic features of concurrent systems, and
demonstrates how techniques such as mutexes can be used to control the problems of con-
current programming. The purpose of this section appears to be to introduce time as another
‘material’ that the programmer can use, and must control . . . indeed, one sub-section is called
‘Time is of the Essence’. This leads on to the presentation of streams (lazy lists) as an alter-
native way of dealing with state – more particularly as another way of encapsulating, and
hence controlling, the use of state. Finally the chapter compares the object, and functional
views of modularity.
This is a book of two halves. The first half consists of the first three chapters, and
corresponds to the ‘Structure’ part of the book’s title. The final two chapters constitute the
‘Interpretation’ of programs. In fact, this division is rather too naive, as both structure and
interpretation are themes throughout the book, but there is a fairly clean change of emphasis
at this point. There has been only one significant change from the first edition: chapter four
now adds a non-deterministic (amb plus backtracking) interpreter, to go with the previous
three (scheme, lazy scheme, and logic programming). These four cover most of the evaluators
that a reader is likely to encounter, and provide an absorbing account of the differences
in philosophy and technology between these paradigms. That it is possible to describe and
implement all these in one language naturally is support for the use of Scheme as the language
discourse for this book.
The final chapter is where the nuts and bolts appear – it deals with implementing all the
previous interpreters by means of register machines. There has been little change from the
first edition, apart from some re-organisation of the topics (which is interesting in itself from
a didactic point of view). Included are sections on compilation and garbage collection.
2 Conclusion
Given that the first edition has been extensively reviewed previously, and is well known, it
is perhaps best to concentrate on the changes when trying to summarise the qualities of this
new edition.
The new material on concurrency is rather a disappointment since such a large and
fundamental topic is relegated to little more than a convenient link item between lists and
streams. Although it’s clear that the authors see the topic as important, the original edition
was conceived without concurrency in mind and, since the original structure is so very strong
and coherent, it is not surprising that it was difficult to integrate it into the book. A radical
rewrite would be required – one can hope that this might be done.
The book has been updated so that the flavour of Scheme used now conforms to the 1990
IEEE standard (in essence, the R4Rs version). At least, most of the code is standard – it is
not clear that the concurrency code will run on “any Scheme implementation conforming to”
the standard, and the graphics additions required to run the picture language examples are
MIT-specific. Both problems can be overcome (either by judicious use of Scheme macros and
continuations, and/or local implementation extensions), but this does make those sections of
the book more or less out of scope for the general reader.
The major omission, and it is major, is that types are never fully, or even adequately
discussed. This can be seen as the only explicit disadvantage in using Scheme. However, on
the positive side, the lack of static types gives educators an excellent way of motivating the
need for careful thought at design time when their students’ programs fail through type errors.
So, who is this book for? It is for anyone who has a need to understand computer science.
It doesn’t tell the whole story – it would be unfair to expect that. However, it does cover most
of the essential parts of the story with a clarity and to a greater depth than any other text.
Reading this book draws you in quite inexorably, reminding you of things you had forgotten,
clarifying things you thought you understood, and showing you how to do things that you
considered too difficult.
If you’re new to SICP, it’s still a ‘must read’ book. However, there is no real need to read
the second edition if you’re familiar with the first, unless you’re going to use it for teaching.
In fact the best feature of the book is that there has been little fundamental change. The
writing style is still exemplary . . . despite the silly jokes; the story is still gripping, epic, and
inspirational; and the examples (and problems) are eclectic. The joy is still apparent. The new
material is interesting, but not outstanding when compared to the ‘core’ of the book. That
said, it’s still one of the best books on computer science . . . it’s the book I would most like to
have written.
Finally, the binding of the softback is terrible! It is cheap and nasty, and nowhere near
robust enough for the sort of use to which the book should be subjected. I already have pages
falling out of the second edition where the first is still going strong!
Alan Wood
and tables, how to use ML modules and the stream-based input/output facilities, and how to
program dialogs. Finally, in Chapter 18 the line is being crossed between a purely expression-
oriented (functional) and an imperative programming style by introducing the notion of a
store. It outlines how values in the store may be created and changed by assignments, and how
the store differs from the environment in which expressions are evaluated. Some pages are
specifically spent on arrays and on how in-place array updating may improve the efficiency
of program execution.
In between, the text includes four problem solving chapters. Using exercises of increasing
complexity and sophistication, they highlight three phases of systematic program development:
problem analysis and formal specification, turning the specification into a working program,
and validating it by means of selected test cases.
A complete specification of the syntax and semantics of SML, of the ML module system
(including modules for sets and tables), and of selected parts of the ML library is given in
appendices.
This is undoubtedly a good text for an SML-based programming course for beginners,
as it neither assumes nor requires much prior knowledge of computer programming, let
alone of the underlying theory. Students may simply work their way step-by-step through
a series of well chosen example programs and programming exercises which should enable
them to develop fairly quickly some hands-on experience and competence in writing compact
programs in an elegant recursive style. It is also worth studying as a complementary text for
an undergraduate course on basic programming (language) concepts.
The emphasis in this text clearly is on ways and means of massaging structured data,
obviously in an effort to acquaint students right from the beginning with the essentials of real
life application programmming. The most important lesson to be learned from this approach
may be that the construction of functions closely follows (in fact, is dictated by) the data
structures they are to be applied to, and that program complexity and efficiency of execution
are intimately related to the choice of a suitable data model.
Unfortunately, the text is not of a comparable quality in the conceptual department, as one
might expect after having read the preface. Fundamental concepts such as functions, pattern
matching, variable bindings or environments are introduced piecemeal, more or less casually
as need arises, and explained only matter-of-factly, but the full picture of the underlying ideas
(why can or should it be done the way it is done) is hardly ever given, not even informally.
To begin with, it is nowhere explicitely stated that SML in large parts is a functional
language (not even when, after 263 pages, the text turns to imperative programming), and
what exactly this means, e.g. absence of side-effects, referential transparency (context-free
substitution of equals by equals), and the role of variables (identifiers) and of variable scoping
in this game. Also, it takes 157 pages until, in a subsection explaining how if then else
clauses need to be correctly evaluated (which, strangely enough, is entitled ‘lazy evaluation’),
some five lines of text can be found stating that SML has a strict (eager) semantics.
The functions used throughout the text are almost all closed – a point that is never
explicitely mentioned, even though this seems to be an essential prerequisite for the correct
workings of the rather simple environments introduced in Chapter 2. When in section 3.7
let-expressions enter the game, it simply says that bindings for let-defined (local) identifiers
are added to the environment in which the entire let-expression is being evaluated. This, in
turn, is supposed to define the environment for evaluating the let-body. At this point, the
conscious reader might ask what happens with identically named identifiers bound both in
the let-expression and in its environment? Likewise, what happens with identifiers that occur
free in local (let-defined) functions and are bound higher up? Answers to these questions
may finally be found in sections 9.9 and 9.10, where closures are introduced as internal
representations of functions. However, rather than giving a full account of what closures are
needed for, the text just describes in a few words how they look like and the mechanics
of evaluating them, using as an example the factorial function which, by all means, is too
trivial to get the complete message across. It is then briefly outlined how closures can be
References
Ullman, J. D. (1994) Elements of ML Programming. Prentice Hall.
Paulson, L. C. (1991) ML for the Working Programmer. Cambridge University Press.
Werner Kluge
Part I. This part introduces the notion of constraints and the use of them for problem
modelling. Constraints are relationships between variables of a particular domain. Examples
are real arithmetic constraints, where the domain is the set of real numbers and primitive
constraints are equalities and inequalities between arithmetic expressions, or finite domain
constraints, where each variable can take a value in a finite set of possible values (usually
natural numbers) and primitive constraints are equalities and inequalities. This part explains
basic notions like solvability or satisfiability, and discusses the heart of any constraint
language, namely the constraint solver. Algorithms for constraint solving and optimization
are provided for various domains, like Gauss–Jordan elimination and the simplex algorithm
for real arithmetic constraints, classical unification for tree constraints, or node and arc
consistency for finite domain constraints.
Part II. This part introduces Constraint Logic Programming (CLP) as a language to provide
rule-based descriptions of constraint problems. Knowledge about logic programming is not
assumed since the authors introduce classical logic programming as constraint logic program-
ming with tree constraints, i.e. constraints over algebraic data terms. The operational behavior
is explained by the construction of derivation trees wrt the set of program rules. This section
provides also a good introduction into programming techniques for constraint programming
by various larger examples, like the computation of tree layouts, designing bridges, or solving
scheduling problems. Since the efficiency of a constraint program heavily depends on the
built-in constraint solver and is, therefore, difficult to determine, the authors discuss in detail
different modellings for a given problem. This is not only a collection of good hints for the
constraint programmer but a necessary technique in practice since the authors admit in § 8.4
that “given the difficulty of understanding interaction between the constraints and the solver
it is often useful to empirically evaluate the different models.” This part of the book finishes
with a discussion of advanced programming techniques, features and implementation aspects
of CLP systems.
Part III. This part provides a survey of constraint programming with other languages than
CLP. This includes constraint databases, where bottom-up evaluation techniques for constraint
programming are presented, as well as the addition of constraints to concurrent, functional,
or imperative languages, or libraries for constraint programming. Unfortunately, the authors
did not include a discussion or references to (constraint) functional logic languages (e.g. see
(Arenas-Sánchez et al., 1999; Hanus, 1994; López Fraguas, 1992)) which are a conservative
extension of the CLP framework and combine the advantages of both functional and logic
programming in a single framework.
This book provides an introduction to the practice of constraint logic programming rather
than a detailed explanation of the constraint logic programming paradigm and its foundations.
The practical orientation of the book is shown by numerous programming examples and a
number of algorithms to explain the implementation of constraint solvers for various domains.
The underlying logical theory of constraint logic programs and the correctness of the presented
algorithms is not shown. This is not necessarily a weak point of the book since including
both aspects of constraint logic programming might be too much material for a single book.
Thus, it is a book written for people interested in the practice of constraint programming and
fills an important gap in the literature in this area. Nevertheless, each section in this book
concludes with relevant bibliographic notes so that the reader interested in more background
material finds appropriate references.
To make the book a useful introduction to the practice of constraint programming, each
section has not only a subsection with exercises but also a subsection with ‘practical exercises’.
Since there is no standard constraint logic programming system or language, the authors
provide in these subsections many hints about obtaining and using different CLP systems
and how the constructs introduced in each section are implemented in the different systems.
These parts make the book a helpful source to the practice of constraint programming.
The developments of the recent years have shown that constraint programming is a new
fascinating programming paradigm with a big potential for practical applications. Unfortu-
nately, there was no good textbook about this area available and the current book fills this
gap. Thus, I can recommend it for everyone who is interested in an area where the idea of
declarative programming has made its way into practice and where there is no question about
the ‘killer applications’ for this programming style.
References
Arenas-Sánchez, P., López-Fraguas, F. J. and Rodrı́guez-Artalejo, M. (1999) Functional plus
logic programming with built-in and symbolic constraints. Proc. International Conference on
Principles and Practice of Declarative Programming (PPDP’99): Lecture Notes in Computer
Science 1702, pp. 152–169. Springer-Verlag.
Hanus, M. (1994) The Integration of Functions into Logic Programming: From Theory to
Practice. J. Logic Programming, V19 & 20, 583–628.
López Fraguas, F. J. (1992) A general scheme for constraint functional logic programming.
Proc. 3rd International Conference on Algebraic and Logic Programming: Lecture Notes in
Computer Science 632, pp. 213–227. Springer-Verlag.
Michael Hanus
Term Rewriting and All That by Franz Baader and Tobias Nipkow,
Cambridge University Press, 1998, ISBN 0-521-45520-0 (hardback), 301pp.
The subject of term rewriting has rapidly matured over the last few decades, as witnessed by
the international RTA and TLCA conferences and the recent emergence of IFIP Working
Group 1.6 on Term Rewriting.
However, until a few years ago there were no comprehensive text books on the subject.
The first and very valuable monograph on term rewriting was published by Avenhaus (1995).
The present book covers more ground, and contains many recent results. It is aimed towards
students as well as researchers. The exterior and layout of the book is pleasant, and the playful
title (suggested by CUP’s David Tranah) is in analogy with an amusing history book: 1066
and All That, that is familiar to many British readers. The material in the book is classroom
tested, and that shows: the style of exposition is clear, and the presentation is well structured.
Many exercises are included, though solutions are not provided.
The contents are as follows: 1. Motivating examples; 2. Abstract reduction systems;
3. Universal algebra; 4. Equational problems; 5. Termination; 6. Confluence; 7. Completion;
8. Gröbner bases and Buchberger’s algorithm; 9. Combination problems; 10. Equational
unification; 11. Extensions; Appendix 1, Ordered sets; Appendix 2, A Bluffer’s Guide to ML;
Bibliography; Index.
A nice feature of the book is the presence of many ML programs, serving to make the
student familiar with notions and algorithms. As the authors put it, the book contains a little
ML-based term rewrite laboratory: it implements terms, substitutions, unification, matching
and term rewriting. Interesting and useful is that the authors treat unification not only on
terms, but also on (acyclic) term graphs. In an exercise (4.27), even the unification of cyclic
term graphs is briefly touched upon. In general, the book is strong in its treatment of various
aspects of unification, including several discussions of complexity issues, and including a
chapter on the fundamental issue of equational unification.
The treatment of termination also does credit to that subject, and is certainly complete
enough for classroom use. After the well known reduction of the undecidability of termination
for term rewriting systems to the (uniform) halting problem for Turing machines, the chapter
treats reduction orders, the interpretation method, polynomial orders, simplification orders,
the beautiful Kruskal tree theorem and well-partial orders, and concludes with recursive path
orders.
The chapters on confluence and completion are also complete and to the point. The
confluence chapter treats not only the classical notion of orthogonality, but includes more
recent theory in a section ‘Beyond orthogonality’, on parallel closure and weak orthogonality.
The chapter on completion treats the important notion of proof orders.
The book gives most of the proofs, but omits the proofs of some theorems such as Toyama’s
theorem on modularity of confluence. Certainly for classroom use this is a good policy.
Chapter 8 is very welcome: it treats the basics of Gröbner bases and Buchberger’s al-
gorithm, which is very prominent in computer algebra. This makes the book interesting to
mathematicians too.
The authors have put some sensible restrictions to themselves (“In der Beschränkung zeigt
sich der Meister”.) They have not tried to give complete coverage to the state of the art in
term rewriting, and thus have excluded some topics that are also of some importance in term
rewriting today. The notable restrictions are: higher-order rewriting (where Nipkow is one
of the main contributors with his HRSs, Higher-order Rewrite Systems); infinitary rewriting;
and term graph rewriting. Some other topics that are only briefly mentioned in the concluding
chapter on Extensions include rewriting modulo an equational theory, strategies for rewriting,
and conditional rewriting. The authors are being very realistic here, and clearly state that
their discussion of these subjects is only meant to whet the reader’s appetite, and provide
pointers to the literature.
Summing up: this is a highly welcome addition to the literature on term rewriting, and
an important contribution to putting term rewriting ‘on the map’. It is a very readable, well
written and likeable book. It should be of great value to students and researchers alike.
Reference
Avenhaus, J. (1995) Reduktionssysteme. Springer-Verlag.