In general, I will not lecture on the material in this book, as it is written for an introductory programming course, and should be understandable by any graduate student in computer science. When there is a tricky point or a dubious exposition, I will try to make an exception. These notes try to clarify or expand on various points, some of which are important. There is an online errata for the text, at
1 It seems not to be very well known that ML first appeared as the
Meta Language of a theorem proving system named LCF (for "logic of
computable functions"), developed at the University of Edinburgh (in Edinburgh
Scotland), or that the original designers of ML were Michael Gordon, Robin
Milner, and Christopher Wadsworth. The original design goal of ML was to be a
higher order functional language for writing programs to do proofs, having a
type system such that only assertions that had complete proofs could be given
the type theorem, and such that theorem proving tactics would be
defined as functions on proofs, with second order functions used for combining
tactics. Originally, LCS was considered much more important than ML, but of
course ML has evolved a good deal since the first publication, Edinburgh
LCF, Lecture Notes in Computer Science, vol. 78, Springer-Verlag, 1979.
Milner was the project leader but Gordon was the point person for ML, and in
particular, he wrote the first ML compiler. The name "Standard ML of New
Jersey" is a joke, based on a once famous company called "Standard Oil of New
Jersey." None of this information appears in Ullman's book, but it is
familiar to me since I was visiting Edinburgh to work with Rod Burstall when
all this was going on.
2 Here are some questions that may aid you in reading Chapter 2: Why does ML have all of tuples, strings, and lists? Why does ML have explicit coercions? Why do binary functions take tuples as arguments? In my opinion, what is remarkable about ML is that these questions (and many others of a similar nature) have good answers, because the language is exceptionally well designed; for example, similar questions about C do not really have good answers.
2.3.3 The box on page 31 seems to claim that ML's val
declaration is side-effect free, but this is arguable, and I am more inclined
to disagree than to agree, although a case for the opposite view can of course
also be made. Consider the following ML code:
val t = 3;
val t = (t,t);
val u = (t,t);
val t = (t,t);
val u = (t,t);
Certainly we get very different values for t and u,
depending on what "assignments" have been done previously. By the way, ML
also has a "real" assignment statement that no one would argue is side-effect
free, so ML is definitely not a pure functional language, although it does
have a very nice functional sublanguage. Even the simplest computation
reveals the presence of certain special (but covenient) side-effects: for
example, 1 + 1; produces the output
val it = 2 : intwhere
it is a built-in variable that stores the result of the
last computation. For example, after executing 1 + 1; we can
execute it + it; which will produce the output
val it = 4 : int
3.2.1 The discussion of what Ullman calls "environments" (and I would call stacks of environments) is easy to follow, but leaves out some extra details needed for the imperative features of ML; Stansifer, Section 5.2, has more detail, more precision, and more generality. Ullman might have mentioned that these clever ideas come to ML from Lisp, and are modified forms of clever ideas for implementing Algol 60, that arose in IFIP WG 2.1. (Stansifer might also have mentioned the role of WG 2.1 in his discussion of block structure in Chapter 5.)
3.3 Ullman is not very good on history; for some historical information on ML pattern matching, see my discussion of section 5.3 of Stansifer.
3.3.4 The "combinations" function comb (which is more
often called the "binomial" function by mathematicians) on page 70, which is
illegal ML, can be written in OBJ,
eq comb(n,n) = 1 .Moreover, one can write the full definition as follows
eq comb(n,0) = 1 .
eq comb(n,n) = 1 .
cq comb(s n, s m) = comb(n, s m) + comb(n, m) if n =/= m .
over the Peano definition of natural numbers, which has the constructors
0 and s. (The condition in the last equation can
be eliminated if we add the equation
cq comb(n,m) = 0 if n < m .instead of just assuming
n => m as a precondition.
5.5 To curry a function is a certain way to get an equivalent
function with domain a function type instead of a product type. For example,
given f of type Int * Int -> Int, we can define the
equivalent function f' of type Int -> (Int -> Int)
by
fun f' m n = f(m,n);Thus,
f'(6) is a function of type Int -> Int. There
is a nice mathematical expression for currying, which also include its
converse, called uncurrying, given by the following isomorphism
[(T1 * T2) -> T] ~ [[T1 -> [T2 -> T]]where
[T] indicates the set of functions having type
T, and ~ indicates isomorphism.
5.6.3 The following definition for map is more
idiomatic ML than the one given by Ullman on p. 177 using let,
although Ullman's version does have some expository value.
fun map F nil = nil
| map F (x :: xs) = F x :: map F xs;
Similarly, the definition of comp in the box on p. 177 is more
idiomatic than Ullman's version using let in Figure 5.20 on
p. 176. (These alternative definitions are more idiomatic because they make
better use of the capabilities of ML.)