% for yTeX
\typesize=11pt
\hsize=34pc
\vsize=50pc
\parskip 6pt plus 2pt

\def\psetheader{
\centerline{MASSACHUSETTS INSTITUTE OF TECHNOLOGY}
\centerline{Department of Electrical Engineering and Computer Science}
\centerline{6.001 Structure and Interpretation of Computer Programs}
\centerline{Fall Semester, 1986}}

\def\code#1{\beginnofill
#1
\endnofill

\vskip .1in}

\rectoleftheader={6.001 -- Fall Semester 1986}
\rectorightheader={Problem Set 1}
\onheaders
\onfooters

\null
\vskip 1truein

\psetheader

\vskip .25truein

\centerline{Problem Set 1}

\vskip 0.25truein

\vpar
Issued:  September 9, 1986


\vpar
Due: in recitation on September 17, 1986 {\it for all sections}

\vpar
Reading: From text, Chapter 1, section 1.1.

Problem sets should always be handed in at recitation.  Late work will
not be accepted.
%  As explained in the Course Organization handout, we
%are staggering the due-dates for problem sets, so as to
%minimize crowding in the lab.  The Wednesday/Friday due dates will be
%reversed during the second half of the semester to assure equitable
%treatment of students in both morning and afternoon recitations.
Specific due dates will be announced as each problem set is handed
out.

Before you can begin work on this assignment, you will need to buy
a copy of the textbook and to pick up
the manual package.  To obtain the manual package, take the coupon
attached, and follow the instructions
given in the handout marked ``6.001: Course Organization.''

\chapter{1. Homework exercises}

Write up and turn in the following exercises from the text:

\beginbullets

\bpar
Exercise 1.1:  Evaluation of expressions

\ftpar{\hskip \parindent}
You may use the computer to check your answers, but first do the
evaluation by hand so as to get practice with Scheme.
Giving forms such as these to the interpreter and observing its
behavior is one of the best ways of learning about Scheme.
Experiment and try your own problems.  

\ftpar{\hskip \parindent}
Though the examples shown are
often indented and printed on several lines for readability, an
expression may be typed on a single line or on several, and redundant
spaces and carriage returns are ignored.  It is to your advantage to
format your work so that you (and others) can easily read it.


\bpar
Exercise 1.3: a test to determine evaulation order

\bpar
Exercise 1.4: new definition of {\it if}

\bpar
Exercise 1.6: Newton's method for cube roots.

\chapter{2. Programming Assignment: Fractal curves}

A substantial part of each problem set consists of a laboratory
assignment that uses the 6.001 computing facility.  
Performance on problems sets, and laboratory assignments in
particular, is a major factor in determining 6.001 grades.
The laboratory assignments have been planned 
on the assumption that the required reading and textbook exercises 
are done before you come to the laboratory.  
You should also read over and think about the laboratory assignment 
before you sit down in front of a Chipmunk.  
It is generally much more efficient to use a Chipmunk to test, debug,
and run a program you have planned before coming into lab than to
try to do the planning ``online''. 
Students who have taken the subject in previous 
terms report that failing to prepare ahead for laboratory assignments
generally ensures the assignments take much longer than necessary.  

You are urged to try to complete laboratory assignments early. 
There is likely to be intense demand
for the laboratory facilities just before assignments are due, 
so it is to your advantage to complete your computer work early.  
You must plan ahead to get access to a Chipmunk because
terminal time is reserved in advance via sign-up sheets in the lab.  

Before coming to laboratory for the first time, you should read the 
``Chipmunk Manual'' and you should bring it with you for reference.  
You may also want to glance through the ``Scheme Manual,'' 
but almost all of the information you need about the Scheme language 
itself has been included in the text.

Before using a Chipmunk, you must insert a floppy disk in the drive.  
Each disk is identified by a name you choose when the disk is 
initialized.  If
you place an uninitialized disk in the drive, the system will offer to
initialize it for you.  Before beginning work on the first assignment,
you should have the system initialize your disk.  More information is
given in the Chipmunk Manual, in the section called ``starting and
leaving the system.''

This first laboratory assignment has been designed to be fairly simple,
so that you will have a chance to become familiar with the Chipmunk
system.
In this assignment, you are to experiment with a simple procedure that
plots a fractal curve known as the C curve.  A level 0 C curve is just
a line.  A level n C curve consists of two level n-1 C curves adjoined
at right angles.  The endpoints of the resulting combination are the
endpoints specified for the level n C curve.  For example, a level 1 C
curve consists of two perpendicular lines, and a level 2 C curve is
one-half of a square.

The following procedure plots C curves.  The arguments to the
procedure are the coordinates of the end points of the curve and the
level number of the curve.

\code{
(define (c-curve x1 y1 x2 y2 level)
  (define (break-line xm ym)
    (c-curve x1 y1 xm ym (- level 1))
    (c-curve xm ym x2 y2 (- level 1)))
  (if (= level 0)
      (draw-line x1 y1 x2 y2)
      (break-line (/ (- (+ x2 x1 y1) y2) 2)
		  (/ (- (+ x2 y1 y2) x1) 2))))}

\code{
(define (draw-line x1 y1 x2 y2)
  (position-pen x1 y1)
  (draw-line-to x2 y2))}

The {\it c-curve} procedure contains a subprocedure, {\it break-line} that
draws the two n-1 level components of an n-level C curve.  The
procedure {\it draw-line} draws a line segment on the display with end
points at the specified coordinates.  The primitive {\it position-pen}
moves the graphics pen to the point indicated by the given
coordinates.  The primitive {\it draw-line-to} moves the graphics pen to
the indicated coordinates, drawing a line from the current point.

\section{Part 1}

Use the NMODE editor to type in the above procedure
definitions.  (Subsequent laboratory assignments will include large
amounts of code, which will be loaded automatically when you begin work
on the assignment.  This time, however, we are asking you to type in
the definition yourself, so that you will get used to the editor.)
Notice that the editor automatically ``pretty prints'' your procedure as
you type it in, indenting lines to the position determined by the
number of unclosed left parentheses.  Notice also that, when you type
a right parenthesis, the matching left parenthesis is briefly
highlighted.  In the code above, words to the right of semicolons are
comments, and need not be typed.  After you have finished entering the
definition, save the procedure on your disk in a file named PS1.
You need not turn in anything for this part.
@end(exercise)

@begin(exercise)
``Zap'' your procedures from the editor into Scheme,@foot{Use the
@c[send buffer] command (@c[shift]-k@-[7] on the Chipmunk keyboard).
This transmits the entire buffer to Scheme, and is the easiest thing
to use when you only have one or two procedures in your buffer.  With
larger amounts of code, it is better to use other ``zap'' commands,
which transmit only selected parts of the buffer, such as individual
procedure definitions.  For a description of these commands, see the
description of NMODE in the Chipmunk manual, under the heading
``Commands for use with Scheme.''} and run it
with test values by executing:
@begin(programexample)
(c-curve -100 -100 100 -100 10)
@end(programexample)

(Press @c[EXECUTE] after typing in the form.)  This should draw a
level 10 C curve.  To see the drawing, press the key marked @c[GRAPHICS] at the
upper right of the keyboard.  The Chipmunk system enables you to view
either text or graphics on the screen, or to see both at once.  This
is controlled by the keys marked @c[GRAPHICS] and @c[ALPHA].  Pressing
@c[GRAPHICS] shows the graphics screen superimposed on the text.  Pressing
@c[GRAPHICS] again hides the text screen and shows only graphics.  @c[ALPHA]
works similarly, showing the text screen and hiding the graphics
screen.

If you typed the program incorrectly, you will need to return to the
editor to make corrections.

You should investigate the behavior of the procedure for other values of the
level argument.  To clear the screen between trials you may execute
the primitive procedure
@begin(programexample)
(clear-graphics)
@end(programexample)
You can get insight into the structure of the C curve by starting with
a level 0 C curve and superimposing a level 1 C curve on that and a level
2 C curve on that, etc.  This can be done by executing @a[c-curve] without
clearing the screen between trials.

If the level argument is too large, it will take a long time to
complete your computation.  It will keep running until you abort
execution by typing control-G.  (The @c[CONTROL] key at the upper left
of the keyboard is used like a @c[SHIFT] key.  To type control-G, hold
down the @c[CTRL] key and press G.)

In doing this problem, you can save typing by taking advantage of the
``line edit'' and ``recall'' features of Scheme.  Pressing the
@c[recall] key will redisplay the last Scheme expression evaluated.
This can then be edited using many of the NMODE edit commands.@foot{In
general, you can use all the edit commands that keep the cursor within
a single line.  An exception: Do not use @c[ctrl]-B if you want to
move the cursor to the left.  Use the left-arrow key.} For instance,
suppose you have just evaluated @a[(c-curve -100 -100 100 -100 5)] 
and you now want to evaluate @a[(c-curve -100 -100 100 -100 10)].  
You can press @a[recall],
use the arrow keys to move the cursor to the character to be changed,
make the edit, and finally press @c[execute] to evaluate the modified
expression.  @c[Recall] always redisplays the previous expression.
Once you have pressed @c[recall], you can repeatedly press
@c[ctrl-recall] to replace this by the next-to-last expression, the
expression before that, and so on, cycling through a ``ring buffer''
of saved expressions.

@end(exercise)

@begin(exercise)

Define a procedure @a[closed-c-curve] that takes the same arguments as
the @a[c-curve] procedure and draws two C curves so that the second
one closes back on the first to produce a figure with 4-fold symmetry.

Hand in a listing of your procedure and a picture of your level 
10 closed C curve.

@end(exercise)

@begin(exercise)
Using the procedure @a[c-curve] as a model, define a procedure,
@a[lc-curve], that given the same arguments as @a[c-curve], 
returns the length of the path that would be traversed by the graphics
pen in drawing the C curve specified.

Hand in a listing of your procedure and
the length of the C curves drawn by  @a[(c-curve -100 -100 100
-100 5)] and  @a[(c-curve -100 -100 100 -100 10)].
@end(exercise)

@begin(exercise)
How many times is the procedure @a[draw-line] executed when the level
argument to the @a[c-curve] procedure is 5, 10 or 20?  Give a
general expression.  Do not try to
do this experimentally for arguments like 20 -- Your expression will
explain why.

How many times is the procedure @a[c-curve] executed when the level
argument to the @a[c-curve] procedure is 0, 1, or 2?  Give a
general expression.
@end(exercise)

@begin(exercise)
When the procedure @a[c-curve] is executed with end point coordinates
@a[x@-[1]], @a[y@-[1]], @a[x@-[2]], and @a[y@-[1]] and with level argument
@a[1], two line segments result, one from @a[x@-[1]] @a[y@-[1]] to
@a[x@-[m]] @a[y@-[m]], and one from @a[x@-[m]] @a[y@-[m]] to
@a[x@-[2]] @a[y@-[2]].  The segments meet at @a[x@-[m]] @a[y@-[m]] in
a right angle.  Show that the values computed by the program for
@a[x@-[m]] @a[y@-[m]] are consistent with this description.
@end(exercise)

@begin(exercise)
Describe how to modify the @a[c-curve] program to allow an arbitrary angle
to be specified for the intersection of the segments at @a[x@-[m]]
@a[y@-[m]].  You need not produce an executable procedure to answer
this question.
@end(exercise)


@section(Adventures in Debugging)

During the semester, you will often need to debug programs that
you write.  This section contains an exercise that you
should work through @i[at the terminal] to acquaint you with some
of the features of Scheme to aid in debugging.  There is
nothing you need turn in for this part of the problem set, but we
strongly suggest that you do this exercise.   Learning to use the
debugging features will save you much grief on later problem
sets.

The file @a[ps1-debug.scm] is a short file that has been provided for
you to load into Scheme.  To do this, use the NMODE extended
command ``load problem set'' and specify that you want to load
the code for Problem Set 1.  This will make a copy of the file on
your disk.

The file contains three tiny procedures called @a[p1], @a[p2], and
@a[p3].  You can examine them using NMODE, but there is no need to do
that now -- you will see them later on, during debugging. Zap the
procedures into Scheme.  Now evaluate the expression @a[(p1 1 2)].
This should signal an error, with the message
@begin(programexample)
Wrong Number of Arguments 1
within procedure #[COMPOUND-PROCEDURE P2]
There is no environment available;
using the current read-eval-print environment.

2 Error->
@end(programexample)

Don't panic.  Beginners have a tendency, when they hit an error,
to quickly type @c[ctrl-G] or press @c[edit]. Then they stare at
their code in the editor until they see the what the bug is.
Indeed, the example here is simple enough so that you probably
can see the bug just by reading the code.  But don't do
this.  Let's instead see how Scheme can be coaxed into producing
some helpful information about the error.

First of all, there is the error message itself.  It tells you
that the error was caused by a procedure being called with 1
argument, which is the wrong number of arguments for that
procedure.  The next line tells you that the error occurred
within the procedure
@a[P2], so the actual error was that @a[P2] was called with 1
argument, which is the wrong number of arguments for @a[P2].

The other information about ``current environment,'' and
``read-eval-print environment'' you can ignore.  We will
learn about environments later in the semester.  Notice, however,
that the prompt now reads
@begin(programexample)
2 Error->
@end(programexample)
which is Scheme's way of indicating that you can now
evaluate expressions within the context of the error, and that
this context is ``at level 2,'' namely, one level down from the
``top level,'' which is the initial Scheme command level.

Unfortunately, the error message alone doesn't say where in
the code the error occurred.  In order to find out more, you need
to use the debugger.  To do this execute the expression @a[(debug)].

@paragraph(Using the debugger)

The debugger allows you to grovel around
examining pieces of the execution in progress, in order to learn
more about what may have caused the error.
When you typed @a[(debug)] just above, Scheme should have responded:
@begin(programexample)
Subproblem Level: 0  Reduction Number: 0
Expression:
(P2 B)
within the procedure P3
applied to (1 2)
Debugger

3 Debug-->
@end(programexample)

This says that the expression that caused the error was
@a[(P2 B)], evaluated within the procedure @a[P3], which was
called with arguments 1 and 2.  That's probably enough
information to let you fix the error, but let's pretend we need
more information, just to learn more about how the debugger works.

Ignore the stuff about Subproblem and Reduction numbers.  (You can
read about them in the Chipmunk manual.)  The prompt says that you are
in the debugger (at level 3, one down from the error level, which was
level 2).  At this point, you could type @c[ctrl]-G to exit the whole
mess and get back to Scheme's ``top level,'' but let's instead explore
the debugger.

The debugger differs from an ordinary Scheme command level, in
that you use single-keystroke commands, rather than typing expressions
and pressing @c[execute].

One thing you can do is move ``backwards'' in the evaluation
sequence, to see how we got to the point that signaled the error.
To do this, type the character @a[b].  Scheme should respond:
@begin(programexample)
Subproblem level: 1 Reduction number: 0
Expression:
(+ (P2 A) (P2 B))
within the procedure P3
applied to (1 2)

3 Debug-->
@end(programexample)
Remember that the expression evaluated to cause the error was @a[(P2
B)].  Now that we have moved ``back,'' we learn that this expression
was being evaluated as a subproblem of evaluating the expression
@a[(+ (P2 A) (P2 B))], still within procedure @a[P3] applied to 1
and 2.

Press @a[b] again, and you should see
@begin(programexample)
Subproblem Level: 2  Reduction Number: 0
Expression:
(+ (P2 X Y) (P3 X Y))
within the procedure P1
applied to (1 2)
@end(programexample)
which tells us that got to the place we just
saw above as a result of trying to evaluate an expression in
@a[P1].

Press @a[b] again and you should see a horrible mess.  What you
are looking at is some of the guts of the Scheme system -- the part
shown here is a
piece of the interpreter's read-eval-print program.  In general,
backing up from any error will eventually land you in the guts of
the system.  At this point you should stop backing up unless, of
course, you want to explore what the system looks like.  (Yes:
almost all of the system is itself a Scheme program.)

In the debugger, the opposite of @a[b] is @a[f], which moves you
``forward.'' Go forward until the point of the actual error,
which is as far as you can go.

Besides @a[b] and @a[f], there about a dozen debugger
single-character commands that perform operations at various
levels of obscurity.  You can see a list of them by typing @a[?]
at the debugger.  For more information, see the Chipmunk manual.

Hopefully, the debugger session has revealed that the bug was in
@a[P3], where the expression @a[(+ (P2 A) (P2 B))] calls @a[P2]
with the wrong number of arguments.@foot{Notice that the call
that produced the error was @a[(P2 B)], and that @a[(P2 A)] would
have also given an error.  This indicates that in this case
Scheme was evaluating the arguments to @a[+] in right-to-left
order, which is something you may not have expected. You should
never write code that depends for its correct execution on the
order of evaluation of the arguments in a combination.  The
``official definition'' of Scheme (whatever that means) does not
guarantee that that any particular order will be followed, nor
even that this order will be the same each time a combination is
evaluated.} You can type @c[ctrl]-G to return to the Scheme top level
interpreter.

@paragraph(The stepper)

Even with such a simple error, debugging was not so simple.
What do you expect?  After all, an error occurred and you
have to grovel through the mess.  An alternative debugging
strategy is to re-evaluate the expression that produced the error, only
this time to do the evaluation ``step by step,'' and observe the
precise point where the error happens.  The Scheme program that
lets you do this is called the @i[stepper].

Let's try to debug again, this time using the stepper.  Clear the
screen (by typing @c[ctrl]-L) and evaluate the expression
@begin(programexample)

(step '(p1 1 2))
@end(programexample)
Don't omit the quotation mark after @a[step].   (You'll learn
about what quotation is for when we get to Chapter 2.)

If you did this correctly, Scheme should type, halfway down the screen,
@begin(programexample)

(DEFINE (P1 X Y) (+ (P2 X Y) (P3 X Y)))
Stepping: (P1 1 2) Eval Combination
@end(programexample)

What you are seeing is the text of the procedure being evaluated,
namely @a[P1].  The next line tells you that the expression being
stepped that called this procedure is @a[(P1 1 2)] and that the
interpreter is at the point ``Eval combination'' which is the
point where the interpreter sets up to evaluate a
combination.  (Later in the semester, we will learn about the
various elements of the evaluation process.)  The cursor is
flashing at the beginning of the combination to be evaluated.

Type a space.  This proceeds the evaluation ``one step.''  Notice
that nothing much happens, except that the cursor moves to the
beginning of the right-hand inner combination, which is the next
thing the interpreter is going to work on.@foot{There's that right-to-left
evaluation order again.  See previous footnote.}

Type another space and the interpreter starts working on the
subexpressions of this combination, starting with the symbol @a[Y].
Accordingly, the stepper prints
@begin(programexample)

Eval Irreducible
Y has the value
2
@end(programexample)
telling you that @a[Y] was irreducible (no subexpressions) and
that it has the value 2.

Type another space and you will see that @a[X] has the value 1.
Another space shows the interpreter determining that @a[P3] is the
name of a procedure.
@begin(programexample)

P3 has the value
#[COMPOUND-PROCEDURE P3]
@end(programexample)

When you type another space, the message changes to show that the
interpreter is about to apply @a[P3] to the arguments 1 and 2.

You now have a choice: you can ask the stepper to do the
application in one step, or you can go step-by-step through the
application.

If you press space, the stepper will do the application in one step and
show the returned value.  Try this.

Ugh!  That signaled the error.  So we know that the error happens
while evaluating @a[(P3 1 2)].  We probably should have gone step
by step through this part of the evaluation.

So go back and try again.  Press @c[ctrl]-G, clear the screen,
execute
@begin(programexample)

(step '(p1 1 2))
@end(programexample)
and press space until you get back to the point where the
interpreter is about to apply @a[P3] to 1 and 2.

This time, instead of pressing the space bar, type @a[s].
The stepper should now show the application of the internal procedure:
@begin(programexample)

(DEFINE (P3 A B) (+ (P2 A) (P2 B)))
@end(programexample)

Another space will get to the application @a[(P2 B)], which gives
the error.

Notice that this is the same information we found with the
debugger above.  Only this time we are working ``forwards'' until
the error occurs instead of letting the error happen and then
working backwards.  Both of these techniques should be part of
your debugging repertoire.


@paragraph(For your amusement)

Edit @a[P3] to eliminate the error by calling @a[P2] with two
arguments each time.  (It doesn't matter what these
arguments are, so long as the applications don't produce errors.) 
Now step through the original call to @a[P1], and get to the point in
@a[P3] where the addition operator @a[+] is about to be applied
to 2 arguments.  If you were to type a space at this point, the
addition would be performed and the result shown.  But, instead
of typing a space, type an @a[s], and you will see some incredibly
obscure stuff.

What you are looking at are the internals of the addition
operation, implemented as a Scheme procedure.  You see, we lied
to you in lecture: @a[+] is not a primitive in the Scheme system
you are using.

The lesson to draw from this is not that we are liars, but that
the meaning of ``primitive'' must always be taken to be relative
a given level of detail at which one wants to look.  Since we
have more than enough material to cover this semester without
worrying about how the 6.001 Scheme system is implemented in
detail, it is convenient for us to consider @a[+] to be a primitive.

For fun, you can start stepping through the application of @a[+].
Each time you get to an internal application, type @a[s] to go
inside of it.  Eventually you will get to a ``real primitive.''
Of course, all that a ``real primitive'' is is something that the
stepper won't let you look inside of.  In terms of the assembly
language ``kernel'' of Scheme, a ``real primitive'' might be a
complex program made up of lots of instructions in assembly
language.  And any single assembly language instruction might be
a complex program written in microcode.

This illustrates a major idea that we will see thoughout 6.001: One
does @i[not] create complex systems by focusing on how to construct
them out of ultimate primitive elements.  Rather, one proceeds in
layers, erecting a series of levels, each of which is the
``primitives'' upon which the next level is constructed.

