# Class 37: Discussion of Exam 3

Held Monday, May 3

Summary

Contents

Handouts

Notes

• Exam 3 has been graded.
• The median grade was 88.5.
• The average grade was 88.
• The first two questions were hard; you should be pround that you did so well as a class.
• In spite of the good outcomes, we'll still spend a little time on the exam today.
• I'll do my best to have the final ready early next week.

## Exam 3

### Simplified Integers

• This problem had a hidden similarity to the next problem; just as it's bad to have ambiguous grammars because they give multiple proofs of the same thing, it's also bad to have Prolog programs with overlapping patterns, since they, too, can give too many proofs (sometimes of the wrong thing).
• I typically did not take off if you had overlapping patterns (and the overlap did not cause problems). However, it is something you should avoid.
• A related problem is not making sure that you had sufficiently many patterns. For example, many of you could not do anything with
```  ?- pred(pred(succ(succ(succ(zero)))), succ(zero))
```
• How should you make sure your patterns are complete? Typically with some careful thought.
• Note that there was a hidden agenda in the problem: making you think about prolog as a model of computation, and not just a way of representing predicate logic.
• A number of you had problems writing ``X and Y have the same canonical form'' (which is one possible definition of `equals`).
• This is wrong:
```equals(X,Y) :- canonical(X,XC), canonical(Y,YC), equals(XC,YC).
```
Why? Because there is an unnecessary (and potentially harmful) recursive call to `equals`.
• This is better. It uses a separate predicate to ensure that the two canonical forms are the same.
```equals(X,Y) :- canonical(X,XC), canonical(Y,YC), same(XC,YC).
same(X,X).
```
• This is best.
```equals(X,Y) :- canonical(X,C), canonical(Y,C).
```
• Wyatt had a particularly elegant implementation of the `equals` predicate.
• ``Move'' all of the operators from the first argument to the second argument (this involves inverting them).
```equals(pred(X),Y) :- equals(X,succ(Y)).
equals(succ(X),Y) :- equals(X,pred(Y)).
```
• We're now in the form `equals(zero,Y)`. We switch to a separate predicate.
```equals(zero,Y) :- balance(zero,Y).
```
• We now treat the two arguments as something like stacks, repeatedly popping things off the second argument and putting them on top of the first argument. When both arguments have the same top, we pop both. We're done when we reach zero in both stacks. Note that I've used nonoverlapping patterns that handle every case.
```/* 0 == 0 */
balance(zero,zero).
/* X-1 == Y-1 iff X = Y */
balance(pred(X),pred(Y)) :- balance(X,Y).
/* X+1 == Y+1 iff X = Y */
balance(succ(X),succ(Y)) :- balance(X,Y).
/* X+1 == Y-1 iff X+2 = Y */
balance(succ(X),pred(Y)) :- balance(succ(succ(X)), Y).
/* X-1 == Y+1 iff X-2 = Y */
balance(pred(X),succ(Y)) :- balance(pred(pred(X)), Y).
/* 0 == Y-1 iff Y == - */
balance(zero,pred(Y)) :- balance(succ(zero), Y).
/* 0 == Y+1 iff Y == -1 */
balance(zero,succ(Y)) :- balance(pred(zero), Y).
```
• My version is wyatt.pro.

### Ambiguity

• Question 2A (the ambiguous parenthetical grammar) had a hidden agenda: getting you to think about grammars which can generate epsilon.
• In particular, the original grammar is not just ambiguous because you can left-associate and right-associate when you have three sets of parentheses, it's also ambiguous because you can add as many extra epsilons as you'd like.
• Consider `()`.
• S => (S) => ()
• S => SS => (S)S => ()S => ()
• S => SS => S(S) =gt; S() => ()
• S => SS => SSS => ...
• Too many of you, in making the grammar unambiguous, also eliminated strings from the language. That's not reasonable. The most commonly eliminated strings were
• The empty string
• `(()())`

### Brackets Grammars

• This was another case in which many of you did not think about all the implications. In particular, some didn't allow close braces. For example, `(()(()]` should be in both of the first languages, but some of you didn't accept it.

### Assignability

• Repeat after me: assignability is not equivalence.

## A Final Note on Call-By-Name

• In spite of its many advantages, call-by-name is not used in a large number of languages.
• Why not?
• Primarily because it's difficult to implement efficiently.
• Often, you need to pass around ``code to execute''.
• This code may need to be passed around at run time.
• It becomes surprisingly difficult to implement `swap(x,y)`, which swaps the values of two variables.
• Why is it difficult? Consider `swap(i,A[i])` and `swap(A[i],i)`
• If we implement `swap` as
```function swap(x,y)
var tmp;
tmp = x;
x = y;
y = tmp;
end
```
• Then the call to `swap(i,A[i])` becomes
```  var tmp;
tmp = i;
i = A[i];
A[i] = tmp;
```
• We're referring to a different index!
• As far as I know, there is no general way around this problem (other than restricting how people use swap).

## Loops

• Many algorithms also need a way to express repetition. It is possible, but not elegant, to express repetition with conditional branches.
• If a language does not provide some form of repetition, then it is not Turing complete.
• Recursion plus conditionals provide one form of repetition.
• A particularly common form of repetition is the loop, which repeats a statement some number of times.
• Loops provide an abstraction of repetition that can help ensure that
• We can only enter the repeated statements in one (or a few ways).
• We can only have one subsequent statement (one that follows the loop).
• There are only a limited number of ways to exit the loop.
• Repetition also introduce some problems into the language. Once programs can repeat parts, it becomes difficult (if not impossible) to prove that a program terminates.
• There are two general kinds of loops:
• `for` loops traditionally repeat a statement (or block of statements) a fixed number of times.
• `while` loops repeat a statement (block of statements) an arbitrary number of times.

### For Loops

• For loops generally express a fixed number of repetitions using a counter variable.
• For loops generally contain five components:
• A counter variable
• A starting value for the counter variable
• An ending value for the counter variable
• An increment to the counter variable
• A statement to execute
• One advantage of such loops is that they are easy to analyze at compile time.
• If the starting value, ending value, and counter are constants, then we can know at compile time how many times the loop will repeat.
• We can unroll such a loop so that we never have to do a test (or even a conditional jump).
• We can guarantee that the loop terminates.
• Some variants of for loops (e.g., those in Java and C) these allow much more general components.
• For example, many languages permit a termination condition rather than an ending value.
• Use of termination conditions obviate the aforementioned benefits.
• For loops also provide a natural way to express a number of algorithms.
• Even without the "extensions" of C and Java, there are a number of design issues in for loops.
• The counter.
• What types are valid for counters?
• Can counters be accessed within the loop? (Usually yes.)
• Can counters be changed within the loop? (Usually no.)
• Is it defined after the loop exits? If so, how? (Usually "unspecified".)
• The bounds.
• Are we restricted to constants, or can we use expressions?
• If we can use expressions, how often are they evaluated? (Only at the beginning of the loop? Each time through? Something else?)
• Can we specify both bounds? Some loops only allow you to specify the upper bound and start with 1 or 0.
• The body.
• Can one exit the loop from within the body?
• Is the body executed if the lower bound is "after" the upper bound?
• There are also a number of variants of for loops. One typical one is the foreach loop, which steps through the elements in a list.
• A related control structure is the iterator which is used to step through elements in a data structure.

### While Loops

• While loops are loops that execute their body until a condition is reached (rather than a fixed number of steps).
• They are more general than for loops, but the number of repetitions usually cannot be predicted in advance.
• In fact, it is impossible to write a program that can read any loop and determine whether the loop terminates. (This is the the halting problem.)
• However, it is often possible to prove that particular loops terminate.
• As in each of the structures we have examined, there are many design decisions. For example,
• Do you execute the body before or after the test?
• If you execute the test first, and it fails, do you still execute the body?
• Can you exit prematurely?
• Because of some of the analysis problems, it is particularly important to carefully write loops using good design. This includes
• Preconditions: what you know before you enter the loop (or any statement within the loop)
• Postconditions: what you know after you leave the loop
• Loop invariants: some fact the loop maintains.

### Guarded Loops

• Dijkstra's guarded loops generalize the guarded conditional to provide looping behavior.
• As with conditionals, guarded loops contain a number of guard/statement pairs.
• If no guards hold, the loop terminates.
• If one guard holds, the corresponding statement is executed.
• If one of the guards hold, one of the corresponding statements is executed.

History

• Created Tuesday, January 19, 1999 as a blank outline.
• Filled in the details on Sunday, May 2, 1999. Most were taken from the previous two outlines.

Disclaimer Often, these pages were created ``on the fly'' with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.