The average for this assignment was 16.42, the median was 17.25, the standard deviation was 4.56, and the highest score was 22.
In the first part of the problem we were looking for some expression of the fact that you would think of attaching properties to program points, showing that these actually held and then using these properties to conclude that the program did what you wanted it to. Note that simply writing down properties at program points is not good enough. It is also necessary to argue that these properties hold!
In the second part we were looking for some expression of the fact that noticing and reasoning about the two loops in the program (line 3 through line 15 and line 8 through line 13) is important and that some invariant property had to be observed What these invariant properties might be can be guessed from the discussion on page 68.
For the last part, the main observation in an answer I would write would be that in checking that a property holds at a particular point it is first of necessary to determine easily how exactly control can get to that point. This is easy to do with structured flow constructs but can become a nightmare with the gotos present in the RAM programs. Incidentally, there is an amusing article on this subject that contributes a ``come-from'' statement to programming languages. This paper appears in the Communications of the ACM, Volume 27, Number 4, April 1984, pages 394-395.
Gotos destroy the simple correspondence between abstract syntax and flow diagrams. A common mistake is to think that this is because an abstract syntax tree cannot be drawn for a program with gotos. This is not true. Thus
goto n
corresponds to an operator (goto) with an argument (n) yielding the
following abstract syntax tree
goto
|
|
n
The problem really is that flow diagrams can no longer be constructed
by a simple composition of parts since the place where to go
to may belong to the flow diagram for another part of the
tree.
Thus consider the program
This code fragment has a loop back to the first statement from its midst that is at least one reason why it is not structured. Following the idea we used in class, we can somehow mark the path that leads to a loop back, proceed to the end of the code and then branch back from there if needed; the rationale for doing this is that then we can capture the structure using something like a repeat. Using a new variable calledl: <alpha>; if <E1> then goto m; <beta>; if <E2> then goto l; <delta>; m: <gamma>;
l: <alpha>;
done = true;
if <E1> then goto m;
<beta>;
if <E2> then done = false;
else { <delta>;
m: <gamma>;
}
if (done == false) then goto l;
But this is the same as
repeat
<alpha>;
done = true;
if <E1> then goto m;
<beta>;
if <E2> then done = false;
else { <delta>;
m: <gamma>;
}
until done;
This code is still not structured because it still has a
repeat
<alpha>;
done = true;
if <E1> then <gamma>
else { <beta>;
if <E2> then done = false;
else { <delta>; <gamma>; }
}
until done;
A common mistake in this kind of problem is assuming that atomic
actions do not affect the tests in the program. This is not an
acceptable assumption. If you think about this a little it means, for
instance, that any program in which a statement is executed more than
once never terminates.
l1:or
/ \
/ \
l2:or l5:T
/ \
/ \
l3:T l4:e1
where e1 is some arbitrary or expression, and l1 through
l5 are 'names' of the nodes.
To decide the value of the top-level node l1, it's left child
l2 has to be evaluated.
Since l2 is yet another or node, the evaluation
process recursively descends to its left child l3. Now
a value T is found, the evaluation result of l2 can be decided
as T, and so does that of l1.
Now suppose the set of rules are changed into
The evaluation order then becomes sort of 'breadth-first'. In the example above, evaluation of l5 should be considered even if we haven't finished evaluating l2. In this case, since l5 is a leaf T, the evaluation result of l1 can already be decided by applying the first rule even without doing any work on l2. Whether you choose to do this or not does not make a whole lot of difference in this particular example, but consider what the situation would be if the computation at l2 turned out to be nonterminating.<beta> or T ---> T T or <beta> ---> T F or F ---> F
The bottomline, if you have appreciated the situation described above, is that the new set of rules when coupled with an outermost strategy calls for a parallel evaluation of the two parts of the or with the ability for each calculation to interrupt the other should it yield T as a result. This is essential because some computations can take infinitely long and, in this case, we would not provide a value for some expressions that do have a value as per our rules if we did otherwise. While it is possible to support such parallel computations, it clearly calls for a more sophisticated machinery than assumed within the framework discussed in class.
Some of you seemed to imply that the old set of rules under outermost evaluation corresponds to eager evaluation whereas the new set of rules under the same strategy is lazy. This is not accurate. Laziness is present in both cases: under the short-circuit evaluation, a right operand is evaluated only when necessary.
All this can be expressed in the form of a recursive function.
Last updated on March 3, 2006 by gopalan@cs.umn.edu and xqi@cs.umn.edu