@comment(Hey, EMACS, this is -*- SCRIBE -*- input)
@make(6001)
@set(chapter=1)
@set(page=1)

@PageHeading(even,
             left "@Value(Page)",
             right "6.001 -- Spring Semester 1984")

@PageHeading(odd,
             Left "Problem Set 5",
             right "@value(page)")

@begin(center)
MASSACHUSETTS INSTITUTE OF TECHNOLOGY
Department of Electrical Engineering and Computer Science
6.001 Structure and Interpretation of Computer Programs

Problem Set 5
@end(center)
@blankspace(0.25 in)

@begin(format)
Issued: March 13, 1984
Due: For all sections, in recitation, Friday March 23, 1984

Reading Assignment: finish Chapter 2, start Chapter 3, through section 3.1
@end(format)
@begin(center)
@b[QUIZ NOTICE]
@end(center)

Quiz #1 will be held on on Monday, March 19, 1984.  The quiz will be
held in Walker Memorial Gymnasium (50-340) from 5-7PM xor 7-9PM.  You
may take the quiz during either one of these two periods, but students
taking the quiz during the first period will not be allowed to leave the
room until the end of the period.  The quiz is open book.  It will cover
material from the beginning of the semester through the material in
problem set 3, and in the notes through section 2.2.1.

@b[Warning:] This assignment is considerably more difficult than the
previous ones.  You will be dealing with a much larger volume of code
than you have until now -- the complete generic arithmetic system
described in Section 2.4 of the notes.  Not only is the system
massive, but it is also ``sophisticated,'' making heavy use of
data-directed techniques.  This is your first assignment where the key
skill we will be working on is the ability to deal with a large
system, assimilating its organization, without being overwhelmed.  The
upshot of all this is that you should be sure to study Sections 2.3
and 2.4 of the notes, and carefully read and think about this handout
before actually attempting to write any code.  The key to this problem
set is understanding the organization well enough to know what you
need to understand and what you need not understand.  Don't be afraid
to ask for help.

@begin(center)
@b[Exercises]
@end(center)

Write up and turn in the following exercises from Chapter 2 of
the course notes:

@begin(itemize)
Exercise 2-45 -- page 122

Exercise 2-48 -- page 125.  This will be especially helpful in
preparing to do the programming assignment.
@end(itemize)

@blankspace(0.25 in)

@begin(center)
@b[Programming assignment]

@b[Rational Functions]
@end(center)

This assignment is based on pages 138 through 141 of the notes, which
ask you to implement a system for dealing with rational functions
(quotients of polynomials).

Here are the pieces of the generic arithmetic system that will be
loaded in for you to work with:

@paragraph(The basic generic system)

We'll begin with a basic system for performing generic arithmetic
operations.  These are defined as follows:
@begin(programexample)
(define (add x y) (operate-2 'add x y))
(define (sub x y) (operate-2 'sub x y))
(define (mul x y) (operate-2 'mul x y))
(define (div x y) (operate-2 'div x y))
(define (=zero? x) (operate '=zero? x))
(define (negate x) (operate 'negate x))
@end(programexample)

Using these we can define compound generic operations, such as:
@begin(programexample)
(define (square x) (mul x x))
@end(programexample)

The basic @a[operate] mechanism is as described in sections 2.3.3 and
2.4.1:
@begin(programexample)
(define (operate op obj)
  (let ((proc (get (type obj) op)))
    (if (not (null? proc))   ;operator is defined on type
        (proc (contents obj))
        (error "Operator undefined on this type -- OPERATE"
               (list op obj)))))

(define (operate-2 op arg1 arg2)
  (let ((t1 (type arg1)))
    (if (eq? t1 (type arg2))
        (let ((proc (get t1 op)))
          (if (not (null? proc)) 
              (proc (contents arg1) (contents arg2))
              (error "Op/type undefined -- OPERATE-2" (list op t1))))
        (error "Operands not of same type -- OPERATE-2"
               (list op arg1 arg2)))))
@end(programexample)
Notice that @a[operate-2] does not coerce between different types.  In
general, we won't worry about coercion in this assignment.

The table operations @a[put] and @a[get] are implemented using
the method described in chapter 3 on page 185 of the notes.  You
needn't worry about understanding this until we get to that chapter.
Just assume that @a[put] and @a[get] are available for you to use:

@begin(programexample)
(define (make-table)
  (let ((local-table (cons '*table* nil)))

    (define (lookup key-1 key-2)
      (let ((subtable (assq key-1 (cdr local-table))))
        (if (null? subtable)
            nil
            (let ((pair (assq key-2 (cdr subtable))))
              (if (null? pair)
                  nil
                  (cdr pair))))))

    (define (insert! key-1 key-2 value)
      (let ((subtable (assq key-1 (cdr local-table))))
        (if (null? subtable)
            (set-cdr! local-table
                      (cons (cons key-1
                                  (cons (cons key-2 value) nil))
                            (cdr local-table)))
            (let ((pair (assq key-2 (cdr subtable))))
              (if (null? pair)
                  (set-cdr! subtable
                            (cons (cons key-2 value)
                                  (cdr subtable)))
                  (set-cdr! pair value))))))

    (define (dispatch m)
      (cond ((eq? m 'lookup-proc) lookup)
            ((eq? m 'insert-proc!) insert!)
            (else (error "Unknown operation -- TABLE" m))))

    dispatch))

(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-table 'insert-proc!))
@end(programexample)

@paragraph(Ordinary numbers)

We'll begin by installing ordinary numbers in the system, as described
in section 2.4.1.

@begin(programexample)
(define (+number x y) (make-number (+ x y)))
(define (-number x y) (make-number (- x y)))
(define (*number x y) (make-number (* x y)))
(define (/number x y) (make-number (/ x y)))
(define (negate-number x) (- x))
(define (=zero-number? x) (= x 0))

(define (make-number x) (attach-type 'number x))

(put 'number 'add +number)
(put 'number 'sub -number)
(put 'number 'mul *number)
(put 'number 'div /number)
(put 'number 'negate negate-number)
(put 'number '=zero? =zero-number?)
@end(programexample)

@paragraph(The bottom level type system)

Rather than using the type operations described on page 115, we make a
modification that allows us to represent ordinary numbers as Scheme
numbers.  For example, rather than having the number 5 represented as
@begin(example)
(number 5)
@end(example)
we will be able to use 5 directly.  An extension of this idea is
discussed in exercise 2-47.

@begin(programexample)
(define (attach-type type contents)
  (if (and (eq? type 'number) (number? contents))
      contents
      (cons type contents)))

(define (type datum)
  (cond ((number? datum) 'number)
        ((not (atom? datum)) (car datum))
        (else (error "Bad typed datum -- TYPE" datum))))

(define (contents datum)
  (cond ((number? datum) datum)
        ((not (atom? datum)) (cdr datum))
        (else (error "Bad typed datum -- CONTENTS" datum))))
@end(programexample)

@paragraph(Polynomials)

Our arithmetic system also includes polynomials, using the
operations described in Section 2.4.3:
@begin(programexample)
(define (+poly p1 p2)
  (if (same-variable? (variable p1) (variable p2))
      (make-polynomial (variable p1)
                       (+terms (term-list p1)
                               (term-list p2)))
      (error "Polys not in same var -- +POLY" (list p1 p2))))

(define (*poly p1 p2)
  (if (same-variable? (variable p1) (variable p2))
      (make-polynomial (variable p1)
                       (*terms (term-list p1)
                               (term-list p2)))
      (error "Polys not in same var -- *POLY" (list p1 p2))))

(define (=zero-poly? p)
  (empty-termlist? (term-list p)))

(put 'polynomial 'add +poly)
(put 'polynomial 'mul *poly)
(put 'polynomial '=zero? =zero-poly?)
@end(programexample)

Polynomial operations are defined using operations on term lists:
@begin(programexample)
(define (+terms l1 l2)
  (cond ((empty-termlist? l1) l2)
        ((empty-termlist? l2) l1)
        (else
         (let ((t1 (first-term l1)) (t2 (first-term l2)))
           (cond ((> (order t1) (order t2))
                  (adjoin-term (order t1)
                               (coeff t1)
                               (+terms (rest-terms l1) l2)))
                 ((> (order t2) (order t1))
                  (adjoin-term (order t2)
                               (coeff t2)
                               (+terms l1 (rest-terms l2))))
                 (else
                  (adjoin-term (order t1)
                               (add (coeff t1) (coeff t2))
                               (+terms (rest-terms l1)
                                       (rest-terms l2)))))))))

(define (*terms l1 l2)
  (if (empty-termlist? l1)
      (the-empty-termlist)
      (+terms (*-term-by-all-terms (first-term l1) l2)
              (*terms (rest-terms l1) l2))))

(define (*-term-by-all-terms t1 l)
  (if (empty-termlist? l)
      (the-empty-termlist)
      (let ((t2 (first-term l)))
        (adjoin-term (+ (order t1) (order t2))
                     (mul (coeff t1) (coeff t2))
                     (*-term-by-all-terms t1 (rest-terms l))))))
@end(programexample)

Term lists are represented as described beginning on page 136:
@begin(programexample)
(define (adjoin-term order coeff l)
  (cond ((=zero? coeff) l)                ;slight simplification
        (else
         (cons (make-term order coeff) l))))

(define (first-term l) (car l))
(define (rest-terms l) (cdr l))
(define (empty-termlist? l) (null? l))
(define (the-empty-termlist) '())

(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))

(define (make-polynomial variable term-list)
  (attach-type 'polynomial (cons variable term-list)))

(define (variable p) (car p))
(define (term-list p) (cdr p))
(define (same-variable? v1 v2) (eq? v1 v2))
@end(programexample)

@paragraph(Rational numbers)

The final piece of our system is a rational number package like the
one described in section 2.1.1.  The difference is that the
arithmetic operations used to combine numerators and denominators are
@a[generic] operations, rather than the primitive @a[+], @a[-], and
@a[*].  This difference is important, because it allows us to work with
``rational numbers'' whose numerators and denominators are arbitrary
algebraic objects, rather than only numbers.  The situation is exactly
analogous to the way that using generic operations in @a[+terms] and
@a[*terms] enable us to work with polynomials with arbitrary
coefficients.

@begin(programexample)
(define (+rat x y)
  (make-rat (add (mul (numer x) (denom y))
                 (mul (denom x) (numer y)))
            (mul (denom x) (denom y))))

(define (-rat x y)
  (make-rat (sub (mul (numer x) (denom y))
                 (mul (denom x) (numer y)))
            (mul (denom x) (denom y))))

(define (*rat x y)
  (make-rat (mul (numer x) (numer y))
            (mul (denom x) (denom y))))

(define (/rat x y)
  (make-rat (mul (numer x) (denom y))
            (mul (denom x) (numer y))))

(define (negate-rat x)
  (make-rat (negate (numer x))
            (denom x)))

(define (=zero?-rat x)
  (=zero? (numer x)))

(put 'rational 'add +rat)
(put 'rational 'sub -rat)
(put 'rational 'mul *rat)
(put 'rational 'div /rat)
(put 'rational 'negate negate-rat)
(put 'rational '=zero? =zero?-rat)
@end(programexample)

The representation of rational numbers is defined as follows:
@begin(programexample)
(define (make-rat n d)
  (let ((g (gcd n d)))
    (attach-type 'rational
                 (cons (quotient n g) (quotient d g)))))

(define (numer q) (car q))
(define (denom q) (cdr q))
@end(programexample)                          

@paragraph(Playing with the generic system)

All of the code above is will be loaded into Scheme when you load the
files for problem set 5.  You will not need to edit any of this code.
We begin with a few simple exercises, to become acquainted with how
the system is used.

@b[Problem 1]: Produce expressions that define
@begin(alphaenumerate)
the rational number 5/6

the polynomial @a[x@+[4] + 3x@+[2] - 4x + 1]

the polynomial @a[(1/2)x@+[4] + (3/5)x@+[2] + (2/3)x + (1/3)]
@end(alphaenumerate)
If your definitions are correct
you should be able to use the generic @a[square] operator to compute
the square of each of these items, and (for more excitement) the
square of the square of each.  Turn in the definitions you typed to
create these objects.

@paragraph(More operations on term lists)

For later use, we will need two more operations on term lists.  The
first, @a[scale-terms] takes a term list and a number as
arguments.  It returns a new term list with terms of the same
order as the original, but where each
coefficient is multiplied by the number.  For example,
@begin(example)
(scale-terms 10 '((2 3) (0 -1))) ==> ((2 30) (0 -10))
@end(example)

@b[Problem 2:] Implement @a[scale-terms].  Using @a[scale-terms] and
@a[+terms], implement a procedure @a[-terms] that subtracts two term
lists.  Use this to implement a polynomial subtraction procedure
@a[-poly], and install this as the generic @a[sub] operation on
polynomials.  Demonstrate that your program works by calling @a[sub]
with two polynomials as arguments.

@paragraph(Dividing polynomials)

@b[Problem 3:] Do exercise 2-61 on page 137 of the notes.
polynomials.  (Erratum: @a[new-o] in the @a[/terms] procedure should
be computed using @a[-] instead of @a[sub] because orders are always
integers.)  To test your program, try
@begin(programexample)
(define p1
  (make-polynomial 'x
                   '((10 1) (9 1) (8 -3) (2 -1) (1 -1) (0 3))))
(define p2 (make-polynomial 'x '((8 1) (0 -1))))
(div p1 p2)
@end(programexample)
What answer do you get?

@paragraph(Polynomial GCDs)

Read the discussion beginning on page 138 of the notes.  Redefine
@a[make-rat] as explained in exercise 2-63, and try the example in
that exercise to make sure that everything works as described.

@b[Problem 4:] Do exercise 2-64.

@b[Problem 5:] Do exercise 2-65.

@b[Problem 6:] Do exercise 2-66.

@paragraph(The rest of this assignment is optional)

Do exercise 2-67.  One point to watch out for is that, due to the
integerizing factors, the coefficients of the intermediate answers
(before the final reduction step) can be enormous -- even hundreds of
digits.  As with the first assignment, numbers this large exceed the
range of Scheme's @a[/] operation.  One way to deal with this problem
is to change @a[/terms] so that the division operation in it will use
@a[quotient] rather than @a[/].  (This works, because multiplying
through by the integerizing factors insures that the @a[div] opration
will only be called when the coefficients are evenly divisible.)  One
way to do this is to change the meaning of @a[div] for integers, so as
to call quotient.

Now do exercise 2-68. Erratum: The polynomial @a[p4] shold have been
defined as
@begin(programexample)
(define p4 (make-polynomial 'x '((2 1)(0 -1))))
@end(programexample)
