Procedures express program structure at a higher level of abstraction than statements, by encapsulating a number of statements, providing a uniform interface to them, and hiding some information about them. The motivation is to make programs easier to understand and to maintain, especially large programs; as usual, part of the idea is to make programmer intent more visible, but abstraction also has other uses. Of course, this will be most successful if the structures involved are already familiar, for example, from the way that stories are structured, or the way mathematical texts are structured. The local identifiers and lexical scope rules of block structure certainly have this property.
The Ada parameter passing modes,
in/out, are a nice feature, because they make the programmer's
intent clearer, and permit the compiler some flexibility in optimizing code.
(Sethi calls the
in/out mode call-by-value-result.)
Lisp was the first language to use recursive procedures, and thus the first that had to worry about how to implement them. If you think of recursion as "problem reduction" in the sense of AI, it is not so difficult to understand as it might seem if you think of it as a general programming features. (In fact, AI problem reduction was McCarthy's motivation for including recursion in Lisp.)
The contour rule (page 169) says that identifiers declared in a given block are only visible inside that block and any other blocks that are nested within it. This idea came from the working group on ALGOL (IFIP WG2.1), as did much other material in this chapter, including the stack discipline for activation records, and the call-by-name semantics for procedure parameters. The word "contour" comes from the idea of drawning "boxes" around blocks in the text of a program, since the lines around such a box are called its contour.
There is a relevant joke about Niklaus Wirth, a member of the Algol 60 design team, and the designer of Pascal, Modula, and Oberon. His name can be confusing for English speakers to pronounce. When asked about the correct pronunciation, he is reported to have said, "If you call me by name, it is is 'virt' and if you call me by value, it is 'worth'." (The correct German pronunciation is 'virt'.)
Text substitution is a form of macro expansion, and is very poor practice for parameter passing, because variables are easily captured by unintended bindings. However, if captured variables are renamed, so that no capture occurs, one gets the form of binding called call-by-name, which was developed for ALGOL60 but is rarely used today. (Call-by-name is closely related to the lambda calculus.)
The metaphor of a "hole" (page 169) in a contour is cute, but unfortunately breaks down when contours are nested to depth 2 or greater, since then one has to think about holes in holes, and holes in holes in holes, and so on, which is a bit confusing, or even mind boggling!
Another form of parameter passing is call-by-need, also called
lazy evaluation; here a parameter is evaluated only if some computation
actually needs it. This notion applies to operations just as well as it does
to procedures. For example,
if_then_else_ needs to evaluate its
first argument, but only needs to evaluate its second argument if its first
true. Lazy evaluation is featured in many modern
functional languages, such as Haskell, but not in ML.
On page 174, line 2, the second right arrow should be a left arrow.
The discussion of strong and weak type systems in Sethi is spread over several different sections of Chapter 5, and is therefore a bit difficult to put together. The following is intended to help with that, as well as to add some details.
A type system is a set of rules that associates types to expressions, and a type system rejects an expression if the rules do not associate a type to that expression (page 137 of Sethi). Usually expressions are defined by a grammar in which (some of) the non-terminals are types, so that an expression is accepted if and only if it can be parsed by the grammar, and then the type system rules associate types to accepted expressions (more precisely, the rules are typically given as an "attribute grammar" in the sense of Section 2 of the class notes). The advantage of a type system defined in this way is that type checking is static, i.e., it can be done at compile time. All type systems seen so far in this course are of this kind.
A type error occurs when a function receives an argument of a type that it does not expect (page 142 of Sethi); this differs from expression rejection, since it refers to a (very limited) aspect of the semantics of functions, namely what types of data they expect as arguments; it does not refer to the syntax of expressions. An expression is type safe if it does not generate a type error when it is evaluated, and a type system is strong if no expression that it accepts causes a type error. A type system that is not strong is called weak. In practice, executing an expression that is not type safe may result in a core dump, an undocumented coercion, a random value, ...
Pages 122-3 of Sethi show that the Pascal type system is weak, because of the way it defines variant records: when the tag field is omitted, it is not possible to check for type errors at compile time. But there are also much simpler (although somewhat artificial) examples of non-safe expressions in weak type systems, such as