CSE 230: Principles of Programming Languages
Notes on Ullman's ML Text

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 : int  
where 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.)

To CSE 230 homepage
To CSE 230 notes page
Maintained by Joseph Goguen
© 2000, 2001, 2002 Joseph Goguen
Last modified: Sun Feb 24 11:10:41 PST 2002