                MASSACHUSETTS INSTITUTE OF TECHNOLOGY

      Department of Electrical Engineering and Computer Science

       6.001 Structure and Interpretation of Computer Programs

                    ******************************
                      problem set 8 for fall 83
                    ******************************

                            Problem Set 6

           Issued: 15 March 1983           Due: 30 March 1983


Readings:
        In chapter 4
		review sections 4.1, 4.2
		also now read section 4.3
	Programs: (attached)
		<ls.source>ps6-putget.scm
		<ls.source>ps6-pps.scm
		<ls.source>ps6-eval.scm
	        <ls.source>ps6-mods.scm

Note: This is a two week problem set!


                              The Story

Ben Bitdiddle, Chief Systems Programmer for the Itsey Bitsey Machine
Company, has just made a terrible discovery: many of his programs are
giving wrong answers.  A hurried investigation reveals that the bugs
are not in Ben's code, but rather in the I-B-M Scientific Subroutine
Library, which the company has subcontracted to a cut-rate software
house.  For example, the SQRT routine supplied in the library, which
Ben has used freely throughout his programs, claims that the square
root of 25 is -6.

Ben is so aghast at this that he decides to adopt a "paranoid
programming style."  With each procedure he suspects of being buggy,
he will associate three sets of predicates that will perform various
consistency checks on the arguments and the returned value of any call
to the procedure:

 PRECONDITIONS -- which are used to check the arguments for applicability.

 POSTCONDITIONS -- which are used to check that the returned value is
                   well-formed.

 TRANSFER CONDITIONS -- These are relations between the arguments and
        the returned value which should be satisfied in any call to
        the procedure.

For example, the SQRT routine should only be called with a non-
negative argument, should always return a non-negative value, and
should have the transfer condition that the square of the returned
value is approximately equal to the argument.  Thus, if we define the
two predicates

(define (non-negative? x) (>= x 0))

(define (square-root-good-enough? v x)
  (define tolerance .001)
  (define (square x) (* x x))
  (< (abs (- (square v) x)) tolerance))

we can use NON-NEGATIVE? as both precondition and postcondition, and 
SQUARE-ROOT-GOOD-ENOUGH? as the transfer condition.

To investigate the feasibility of this idea, Ben decides to implement
a PPS (paranoid programming system) for procedures of one argument.
Given a procedure of one argument, he writes a program that constructs
a "careful version" of the procedure, which performs checks that have
been indicated by appropriate declarations.  For example, Ben
constructs a careful version of SQRT as follows:

(define csqrt (careful-version sqrt))

(declare preconditions csqrt non-negative?)
(declare postconditions csqrt non-negative?)
(declare transfer-conditions csqrt square-root-good-enough?)

Ben's CAREFUL-VERSION constructor takes as input a suspect
one-argument procedure and returns as its value another one-argument
procedure, which, when applied, runs the original procedure and also
checks the declared conditions, signalling an error if any condition
fails.  Declarations associate conditions with procedures, and these
can be accessed via selectors called PRECONDITIONS, POSTCONDITIONS,
and TRANSFER-CONDITIONS.  In principle, a single procedure may have
many declared pre/post/transfer conditions.  Thus, PRECONDITIONS,
POSTCONDITIONS, and TRANSFER-CONDITIONS each return a list of
predicates.  The original suspect procedure is also associated with
the careful version, and can be retrieved using a selector called
KERNEL-PROCEDURE.

(define (careful-version suspect-procedure)
  (define (me x)                      ;ME will be the careful version
    (check-all (preconditions me) (lambda (pred) (pred x)))
    (let ((v (suspect-procedure x)))
      (check-all (postconditions me) (lambda (pred) (pred v)))
      (check-all (transfer-conditions me) (lambda (pred) (pred v x)))
      v))
  (set!-kernel-procedure me suspect-procedure)
  me)

CHECK-ALL takes as arguments a set of predicates (represented as a
list) and a specification of how to apply each predicate to arguments.
It applies each predicate in the set, and signals an error if any
predicate returns NIL.

(define (check-all set-of-predicates predicate-application-form)
  (define (loop s)
    (cond ((null? s) t)
          ((predicate-application-form (car s)) (loop (cdr s)))
          (else (error "Failed consistency check" (car s)))))
  (loop set-of-predicates))

The actual declarations and associations are accomplished using a
general get/put! mechanism, similar to the one used for implementing
generic operators in Chapter 2 of the notes.  One stylistic difference
is that the keys into the table are not symbols, but rather the actual
procedure objects.

(define (preconditions proc)       
  (get proc preconditions))        

(define (postconditions proc)
  (get proc postconditions))

(define (transfer-conditions proc)
  (get proc transfer-conditions))

(define (kernel-procedure f)
  (get f kernel-procedure))

(define (declare declaration-type proc predicate-procedure)
  (let ((set (get proc declaration-type)))
    (if (not (memq predicate-procedure set))
        (put! proc declaration-type (cons predicate-procedure set)))
    'OK))

(define (set!-kernel-procedure f1 f2)
  (put! f1 kernel-procedure f2))

Finally, here is the PUT!/GET table mechanism used in implementing
the system.

(define (get key-1 key-2)
  (let ((subtable (assq key-2 (cdr *get/put!-table*))))
    (if (null? subtable)
        nil
        (let ((pcell (assq key-1 (cdr subtable))))
          (if (null? pcell)
              nil
              (cdr pcell))))))

(define (put! key-1 key-2 value)
  (let ((subtable (assq key-2 (cdr *get/put!-table*))))
    (if (null? subtable)
        (set!-cdr *get/put!-table*
                  (cons (cons key-2
                              (cons (cons key-1 value) nil))
                        (cdr *get/put!-table*)))
        (let ((pcell (assq key-1 (cdr subtable))))
          (if (null? pcell)
              (set!-cdr subtable
                        (cons (cons key-1 value)
                              (cdr subtable)))
              (set!-cdr pcell value))))))

(define *get/put!-table* (list '*get/put!-table*))

Ben's Paranoid Programming System works very well -- so well that
I-B-M begins using it for their own internal software development (to
protect them, for example, from Louis Reasoner's code.  Louis is a
trainee working for Alyssa P. Hacker, who works for Ben Bitdiddle.
His code usually contains bugs.).

One common thing that people do at I-B-M is to combine procedures to
make more complex procedures.  For instance, given an ABS procedure
that computes absolute values, and a SQRT procedure that computes
square roots, one can form a composition SQRT-ABS that computes the
square root of the absolute of its input.

(define sqrt-abs (compose sqrt abs))

where COMPOSE is defined as

(define (compose f g)
  (lambda (x) (f (g x))))

Ben notices that if we use COMPOSE with careful procedures, this can
lead to redundant testing of preconditions and postconditions.  For
example, suppose that CABS -- the careful version of ABS -- has a
postcondition asserting that the returned value is non-negative.

(define cabs (careful-version abs))
(declare postconditions cabs non-negative?)

Then, CSQRT-ABS, defined as the composition of CSQRT and CABS, will
test that the value returned by CABS is non-negative twice, once as a
postcondition of CABS, and once as a precondition of CSQRT.  To avoid
this wasteful computation, Ben proposes the following composition
operator for careful procedures:

(define (careful-compose f g)
  (define (kernel x)
    (let ((vg ((kernel-procedure g) x)))
      (check-all (postconditions g) (lambda (p) (p vg)))
      (check-all (transfer-conditions g) (lambda (p) (p vg x)))
      (check-all (set-difference (preconditions f) (postconditions g))
                 (lambda (p) (p vg)))
      (let ((vf ((kernel-procedure f) vg)))     ;Compute value of F
        (check-all (transfer-conditions f) (lambda (p) (p vf vg)))
        vf)))
  (define (me x)
    (check-all (preconditions g) (lambda (p) (p x)))
    (let ((vme (kernel x)))
      (check-all (postconditions f) (lambda (p) (p vme)))
      vme))
  (set!-kernel-procedure me kernel)
  (put! me preconditions (preconditions g))
  (put! me postconditions (postconditions f))
  me)

The idea is that the careful composition of F and G should check the
preconditions, postconditions, and transfer conditions for G, the
postconditions and transfer conditions of F, but only those
preconditions for F that are not automatically guaranteed by the
postconditions of G.  The resulting careful composition has as its
preconditions the preconditions of G, as its postconditions the
postconditions of F, and as its kernel procedure a procedure that
computes value of the composition and performs all necessary checks
not included in the pre- and post- conditions of the composition.

Ben eliminates redundant conditions on the interface between F and G
by using a SET-DIFFERENCE operator which returns all the elements of a
set S1 which are not contained in a set S2:

(define (set-difference s1 s2)
  (cond ((null? s1) nil)
        ((memq (car s1) s2) (set-difference (cdr s1) s2))
        (else (cons (car s1) (set-difference (cdr s1) s2)))))

                          Warm-up Exercises

[Exercise 1]
	One of the suspect procedures in the subroutine library, SORT,
takes as argument a list of distinct numbers, and claims to return a
list of the same numbers, sorted into ascending order, for example

(sort '(2 8 5 3)) ==> (2 3 5 8)

Alyssa P. Hacker, tries to make a careful version of SORT, using his
Paranoid Programming System.  She constructs the careful version,
CSORT, using the predicates LIST-OF-NUMBERS? and ASCENDING-ORDER?
which were written for Alyssa by her trainee, Louis Reasoner.

(define csort (careful-version sort))

;;;LIST-OF-NUMBERS checks that its argument is a list, all of whose
;;;elements are numbers

(define (list-of-numbers? x)
  (and (list? x)
       (or (null? x)
           (and (number? (car x))
                (list-of-numbers? (cdr x))))))

;;;ASCENDING-ORDER? checks that the elements of its input list are in
;;;ascending order

(define (ascending-order? x)
  (or (null? x)
      (and (< (car x) (cadr x))
           (ascending-order? (cdr x)))))

(declare preconditions csort list-of-numbers?)
(declare postconditions csort list-of-numbers?)
(declare postconditions csort ascending-order?)

Unfortunately, Louis' ASCENDING-ORDER? procedure has a bug.

1.  Explain what the bug is.  Hint: Describe what the procedure will
do, given the list (1).

2.  This program can be repaired by adding an additional short clause
to the OR.  What is the required clause?

[Exercise 2]
	Louis fixed his code, and ASCENDING-ORDER? worked as intended.
Unfortunately this was not enough to ensure correct SORT behavior.
The CSORT routine still did not work, though the declarations were
satisfied.  The behavior was:

(csort '(4 6 5 9)) --> (5 6 9)

Ben recommended that an appropriate transfer condition would catch
this bug.  Alyssa suggested checking that the input and output lists
have the same length, but Ben said this would not be not a sufficient
test to absolutely guarantee that CSORT was not failing.

Explain why Ben is correct, giving a particular example of an
incorrect value that might be returned by CSORT that would not be
caught by Alyssa's suggestion.

Produce an appropriate transfer predicate, which, when declared as a
transfer condition for CSORT will guarantee to catch any incorrect
answers.  Give

  1. a definition of the transfer predicate, in Scheme

  2. a brief statement of what condition the predicate is checking.

  3. the declaration required to add this transfer condition to CSORT.


[Exercise 3]
	Alyssa asks Ben to explain why his CAREFUL-COMPOSE method is
useful.  Ben illustrates as follows:

Consider computing big roots of numbers by repeatedly taking square
roots:

(define (big-root n) (repeated n sqrt))

(define (repeated n f)
  (cond ((= n 1) f)
        ((even? n)
         (let ((g (repeated (/ n 2) f)))
           (compose g g)))
        (else (compose f (repeated (-1+ n) f)))))

Suppose we form a careful big-root procedure by repeatedly composing
the careful procedure CSQRT, which is defined to have NON-NEGATIVE?
both as a precondition and a postcondition:

(define (c-big-root n) (repeated n csqrt))

The number of times that NON-NEGATIVE? is tested in computing big
roots would be considerably reduced if we had used careful-compose
rather than COMPOSE in our definition of REPEATED.

Show that Ben is right, as follows:

Suppose we define C-16TH-ROOT to be (C-BIG-ROOT 4)

	[A] How many times will NON-NEGATIVE? be called in
(C-16TH-ROOT 2) with REPEATED defined using COMPOSE?  Explain your
answer.

	[B] How many times will NON-NEGATIVE? be called in
(C-16TH-ROOT 2) with REPEATED defined using CAREFUL-COMPOSE?  Explain
your answer.

These are tricky, so be careful.  You may check your answers by
instrumenting the PPS and running it, if you like.


[Exercise 4]
	Ben's idea makes programs so reliable that Oliver Warbucks,
the president of I-B-M, decrees that condition testing should be
installed into the I-B-M Scheme interpreter.  Ben and Alyssa realize
that this will make the interpeter so inefficient that they refuse to
do the job, and go off to work for the Hot-Tari company to make video
games.  Poor Louis Reasoner is the only one left at I-B-M to install
the new interpreter, and he desperately needs our help.

In the new version of Scheme, the CAREFUL-VERSION constructor is not
used at all.  Every procedure is potentially a careful procedure, with
associated pre- post- and transfer conditions.  Moreover, the
"careful" mechanism will be extended to work for procedures of any
number of arguments.  A procedure's precondition will be a predicate
that takes the same arguments as the procedure.  The postcondition is
still a procedure of one argument (the value returned by the
procedure).  The transfer condition for a procedure of N arguments is
a procedure of N+1 arguments -- the returned value, followed by the
procedure arguments.

Louis begins by changing the meta-circular evaluator EVAL procedure
(indicated in the starred line below) to call a special CAREFUL-APPLY
rather than the old APPLY:

(define (eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((quoted? exp) (text-of-quotation exp))
        ((variable? exp) (lookup-variable-value exp env))
        ((definition? exp) (eval-definition exp env))
        ((assignment? exp) (eval-assignment exp env))
        ((lambda? exp) (make-procedure exp env))
        ((conditional? exp) (eval-cond (clauses exp) env))
        ((application? exp)
         (careful-apply (eval (operator exp) env)	;*****
			(list-of-values (operands exp) env)))
        (else (error "Unknown expression type -- EVAL"))))

CAREFUL-APPLY uses the ordinary APPLY in order to apply procedures.
However, if the procedure has any associated declarations,
CAREFUL-APPLY must check these.

(define (careful-apply procedure arguments)
  <part 1: check preconditions, if any>
  (if (not (and (null? (postconditions procedure))
		(null? (transfer-conditions procedure))))
      (let ((val (apply procedure arguments)))
	<part 2: check postconditions, if any>
        <part 3: check transfer conditions, if any>
	val)
      (apply procedure arguments)))


Give the missing expressions part 1, part 2, part 3 indicated above.


[Exercise 5]
	Cy D. Fect, the local ALGOL and FORTRAN wizard, asked Louis
why he uses an explicit IF test to see if there are any transfer and
postconditions and skip to APPLY if not.  After all, he notices, the
expressions part 1, part 2, and part 3 should have no effect if there
are no relevant conditions.  Louis says that he doesn't understand
this either, but that before Ben left, he muttered something about
doing things this way so as not to destroy the tail recursive
properties of the language any more than necessary.  Please explain
what Ben was talking about.

                              More Story

Having lost his most talented programming staff, Oliver has decided
that it is time to tighten management controls in his programming
department.  He never liked the free style of Ben and his employees.
Cy D. Fect, who is now the senior member of the staff, and who never
liked LISP anyway, convinces Oliver to call in an expensive
consultant, Herr Professor N. Worthless, D.Ing., to evaluate the
situation.  Worthless is the internationally renowned inventor of the
famous programming language, PASQUAL, which was designed to force
programmers to write "well-structured" programs.

Worthless tells Oliver that SCHEME (and all LISPs) is a terrible
programming language because it allows programmers to write programs
which are not "type-safe" -- a program may be passed an argument which
is of the wrong type.  For example, SQRT can be passed a list, which
it is not prepared to handle.  He believes in a strong type system
which can prevent such a problem.  In a strongly typed language, every
variable is declared to have a type, which constrains what its value
must be.  In addition, every procedure which returns a value (a
"function") must also have a declared type of its value.

Louis showed Worthless Ben's PPS system, which Worthless dismissed
lightly, "As in all LISPish systems, it is very pretty, but the fatal
flaw is that the declarations are optional.  For effective software
management it is necessary to make the declarations a mandatory part
of every program.  We must outlaw bad programs and punish bad
programmers.  Only then will we have effective management.  In fact,
PPS encourages sloppy work because it makes it too easy to find errors
at run-time and fix them.  This may be good in a research environment,
where the programs are continually under development and the users are
the programmers, but in the situation where we want to sell a product,
we want to be sure it is solid before it gets to the market."

This was pretty convincing to Oliver and Cy, but the company could not
easily switch to PASQUAL because they had a huge investment in SCHEME
software which they had already written.  Specifically, they make
heavy use of procedural arguments (which work pretty well in PASQUAL)
and procedural values (which don't work at all in PASQUAL).  Worthless
proposes a modification to SCHEME, which enforces strong typing, which
they call PASCHEME.  In PASCHEME, every procedure is defined with a
type declaration for each argument, and with a type declaration for
the value.  The types are represented as predicates which test if an
object is of the required type.  Syntactically, a procedure will be
defined with types declared as follows:

(define <type of value>
        (procedure-name (<type of arg1> <arg1>)
			(<type of arg2> <arg2>)
			...)
  <body>)

For example, in PASCHEME we would writhe:

(define integer? (factorial (integer? n))
  (cond ((= n 0) 1)
	(else (* n (factorial (- n 1))))))

In addition, DEFINE must be extended to allow the definition of
variables which are not procedures, as in the SCHEME definition
(DEFINE A 3).  The extension compatible with this here is:

(define integer? a 3)

We will start with every type being a predicate in the underlying
SCHEME system (the implementation language), not a user-definable
PASCHEME predicate.


                          In the Laboratory

In this assignment we will be helping Louis implement the changes to
SCHEME required to build PASCHEME.  The file <LS.SOURCE>PS6-EVAL.SCM
contains a copy of a simple SCHEME interpreter in SCHEME, essentially
identical to the one in Section 4.3.  There are a few minor
differences, of which the most important are:

    The procedures EVAL and APPLY have been renamed MINI-EVAL and
    MINI-APPLY, so as not to interfere with Scheme's own EVAL and
    APPLY operators.

    The interface to the underlying Scheme system via
    APPLY-PRIMITIVE-PROCEDURE is handled somewhat differently from in
    the notes.

    The basic driver loop is defined as

      (define (driver-loop)
        (user-print (mini-eval (read '**==>) the-global-environment))
        (driver-loop))

    where USER-PRINT is a procedure that takes care not to try and
    print the environment part of a procedure, since that is often a
    circular structure.

See the attached code for more details.

If you load this file into Scheme and type INITIALIZE-LISP, you will
find yourself typing at the driver loop.  Please note that this Lisp
running in Lisp contains no error system.  If you hit an error or type
ctrl-G, you will bounce back into Scheme.  Also, you must type in
function definitions directly to the driver loop, since there is no
interface to EMACS.  In order to help you keep from becoming confused,
the driver loop uses the prompt **==> instead of the ordinary scheme
prompt ==>.

Start up the interpreter and try a few simple expressions.  If you
bounce out into Scheme, you can re-enter the interpreter by typing
(DRIVER-LOOP).  If you get hopelessly fouled up, you can run
INITIALIZE-LISP, but this initializes the environment, and you will
lose any definitions you have made.

Also, it is instructive to run the interpreter while tracing MINI-EVAL
and/or MINI-APPLY, to see just how the evaluator works.  (You will
also probably need to do this while debugging your code for this
assignment.)  If you do trace these functions, be sure to reset the
Scheme variables *PRINT-BREADTH* and *PRINT-DEPTH* to reasonably small
values, e.g., 5, because the ENV argument to these procedures (printed
by the tracer) is in general a circular list.

For this assignment, you will need to modify a few of the procedures
in the interpreter, and also write a few new procedures.  To help save
disk space, the file <LS.SOURCE>PS6-MODS.SCM contains most of the
procedures that you will need to modify.  You can load this file into
an EMACS buffer, which you can edit while doing this assignment.  The
entire PS6-EVAL file should be LOADed directly into Scheme and not
copied onto your directory.

Strategy:
	The changes required are of two basic sorts.  There are
"syntactic" changes, which concern the ways in which program
expressions are symbolically represented, and "semantic" changes,
which concern the actual computations which will be performed.  In
general, syntactic changes are made in the constructors, selectors,
and predicates which determine the "syntax" of the language.  Semantic
changes will be made in the content procedures MINI-EVAL and
MINI-APPLY and their helper procedures.  Syntax and semantics are not
completely independent; they interact in the representation of the
computational objects manipulated by the interpreter, such as
procedure objects.

In particular, in this interpreter, procedure objects are represented
by "closed LAMBDA expressions" (often called "closures") which are a
combination of a LAMBDA expression text and an environment specifying
the meanings of free variables.  We will need to augment the
representation of a LAMBDA expression to admit the type information.

I propose that LAMBDA expressions be extended to look like:

(LAMBDA <type of value>
	((<type of arg1> <var1>) ... (<type of argn> <varn>))
	<body expressions>)

Thus the LAMBDA expression for the FACTORIAL procedure will now look
like:

(lambda integer? ((integer? n))
	(cond ((= n 0) 1)
	      (else (* n (factorial (- n 1))))))


[Part 1]
	Change the interpreter syntax so that DEFINE expressions and
LAMBDA expressions have typed variables and a typed value.  This has
several pieces.

	[A] Change the procedure-object selectors PROCEDURE-PARAMETERS
and PROCEDURE-BODY to correctly accommodate the new structure.  The
PROCEDURE-PARAMETERS should return the entire typed parameter list --
do not strip off the types here.  In addition, define a new selector,
PROCEDURE-VALUE-TYPE which gets the type predicate expression for the
value of a procedure.

	[B] Change DEFINITION-VARIABLE and DEFINITION-VALUE to parse
the new definition syntax.  DEFINITION-VARIABLE must supply a typed
variable for the thing being defined.  There are two cases:

In the case that we are defining a simple variable to have a value, we
supply the type explicitly.  For example:

For (DEFINE INTEGER? A 3), DEFINITION-VARIABLE should deliver up
(INTEGER? A) and DEFINITION-VALUE should deliver up 3.

In the case that we are defining a new compound procedure we must
supply a type, PASCHEME-PROCEDURE? for the procedure variable being
defined.  For example:

For (DEFINE INTEGER? (FACTORIAL ...) ...), DEFINITION-VARIABLE should
deliver up (PASCHEME-PROCEDURE? FACTORIAL) and DEFINITION-VALUE should
make an appropriate LAMBDA expression.

	[C] An environment will be made by EXTEND-ENVIRONMENT by
pairing the elements of the typed formal parameter list of the lambda
expresion with their values.  An environment will have types in the
environment entries.  These types will be used, in assignments, to
check that values assigned to the variables are correct.  Change the
procedures which define the representation of environments to
implement the new features.

	[D] Change the initial global environment to have an
appropriate type -- PASCHEME-PROCEDURE? for each of the initial entries.

Your modified evaluator should now work as it did before.  It should
take the typed syntax and ignore the types, except in the building and
accessing of elements in the environment.  It is important to test
this out before going on to the next step!  Otherwise you may become
horribly confused.  You should hand in listings of the changes you
made and also a demonstration of the interpreter working at this
point.


[Part 2]
	We will now change the interpreter semantics so that the types
are checked when the program is run.  A type is any predicate of the
underlying SCHEME implementation language.  Thus a type predicate will
be applied to its arguments using the underlying SCHEME's APPLY.  In
fact, since we will have to get at the SCHEME value of the type
predicate name, so we can have something to APPLY, we will have to use
SCHEME's EVAL as well.  To simplify matters, you may use the following
piece of magic to SCHEME apply the SCHEME value of a symbol
representing a unary (one argument) predicate to its arguments:

(define (apply-unary-predicate-symbol predicate-name argument)
  (apply (eval predicate-name (make-environment))
	 (list argument)))

Later we will concern ourselves with the problem of allowing a user to
define new types, as PASCHEME predicates.


	[A] Change EXTEND-ENVIRONMENT to check types of arguments as
they are being bound to formal parameters.

	[B] Change MINI-APPLY to check types of values being returned
from procedures.  This will break the tail-recursive property of the
language, making it impossible to define iterative procedures -- why?

	[C] Change the way assignments and definitions are executed so
that types are checked on assignment of a value to a variable.

	[D] We will have to define PASCHEME-PROCEDURE? as a type
predicate if we wish to pass PASCHEME procedures around as arguments.
Do it.

At this point you should be able to run a program again.  Debug your
changes in the context of the simple recursive FACTORIAL procedure.
Pass it as an argument to some other procedure and demonstrate its
use.  Supply us listings of your changes and a demonstration that they
work.  Show that your interpreter checks types on input and output.


The loss of tail-recursive definitions of iterative procedures, such
as LOOP in the SCHEME iterative factorial below, is a serious problem.
In the next part we will see what can be done to alleviate the
situation, and its consequences.

(define (factorial n)
  (define (loop count answer)
    (cond ((> count n) answer)
	  (else (loop (+ count 1) (* count answer)))))
  (loop 1 1))

One idea is to allow a special declaration, NOVALUE, for the type of
the value of a procedure.  A procedure marked NOVALUE is explicitly
noted in MINI-APPLY and its value is not checked.  This leaves us in a
bad position, however, how are we to get a value out of an iteration?
The traditional way this is done in ALGOL-like languages such as
PASQUAL is by assignment to a value-collection variable.  We write:

(define integer? (factorial (integer? n))
  (define integer? answer 1)
  (define novalue (loop (integer? count))
    (cond ((> count n) 'done)	;value is ignored.
	  (else (set! answer (* count answer))
		(loop (+ count 1)))))
  (loop 1)
  answer)

This programming style makes Cy very happy, indeed.

[Part 3]
	You are to implement the NOVALUE mechanism in MINI-APPLY so
that iterative procedures in SCHEME can be translated to PASCHEME as
shown above and will work iteratively in PASCHEME.  Hand in your
modification to MINI-APPLY and some examples of use.

                               Epilogue

One evening, after work, Ben, Alyssa, and Louis get together for a few
beers.  Louis, who has learned a great deal, but doesn't like what's
going on at I-B-M, shows his old friends the PASCHEME system.  "Seems
like old man Warbucks has become a real fascist!", Ben exclaims.  "And
a stupid one too", Alyssa noted, looking up from her beer.  "He thinks
his system is type-safe, but it is full of holes.  For example, the
NOVALUE construct cannot really work.  If he allows that, how can he
prevent me from just using the value without checking, like we used
too."  She scribbled on a napkin, "Consider the following program:"

(define integer? (factorial (integer? n))
  (define novalue (loop (integer? count) (integer? answer))
    (cond ((> count n) answer)
	  (else (loop (+ count 1) (* count answer)))))
  (loop 1))

"I could return anything I like from LOOP, but I cannot see how the
system could possibly prevent me from using its value."  Louis thought
awhile and suggested that the system could check that the value of a
NOVALUE procedure must be DONE.  Ben responded that of course that
would break tail recursion.  Louis agreed, but then he pointed out
that at least in this case the damage was not serious because the
caller, FACTORIAL, checked the type.

[Reflection 1]
	Write a paragraph or two giving your views on this matter.
Just how safe is this system?  Can you find any really big hole in the
type system?  Is it possible to do better?


Ben noted that the system was not very flexible.  Since types are
SCHEME predicates, there is no good way to define a new one.  This is
a real problem.  

[Reflection 2]
	Write a short paragraph discussing the major changes required
to make PASCHEME have an extensible type system.

"In fact", Alyssa says, "there is no way to make a type be a definable
predicate unless the system has a hole to allow a type predicate to
accept an argument of any type at all.  Thus the system would have to
have such a hole to have extensible types."  Ben disagrees.  He
believes that it is possible to make MINI-APPLY and EXTEND-ENVIRONMENT
treat the application of type predicates specially, so that one can
define new ones which accept any type of argument, without further
violating the type system.

[Reflection 3]
	Is Ben right?  What do you think?  Here we want another short
paragraph describing your views.
