sicp/2-4.org

17 KiB
Raw Blame History

2.4 - Multiple Representations for Abstract Data

Representations for Complex Numbers

Tagged Data

  ;; ===================================================================
  ;; 2.4.2: Tagged Data
  ;; ===================================================================

  (define (attach-tag type-tag contents)
    (cons type-tag contents))

  (define (type-tag datum)
    (if (pair? datum)
        (car datum)
        (error "Bad tagged datum -- TYPE-TAG" datum)))

  (define (contents datum)
    (if (pair? datum)
        (cdr datum)
        (error "Bad tagged datum -- CONTENTS" datum)))

  (define (rectangular? z)
    (eq? (type-tag z) 'rectangular))

  (define (polar? z)
    (eq? (type-tag z) 'polar))

  (define (real-part-rectangular z) (car z))

  (define (imag-part-rectangular z) (cdr z))

  (define (magnitude-rectangular z)
    (sqrt (+ (square (real-part-rectangular z))
             (square (imag-part-rectangular z)))))

  (define (angle-rectangular z)
    (atan (imag-part-rectangular z)
          (real-part-rectangular z)))

  (define (make-from-real-imag-rectangular x y)
    (attach-tag 'rectangular (cons x y)))

  (define (make-from-mag-ang-rectangular r a)
    (attach-tag 'rectangular
                (cons (* r (cos a)) (* r (sin a)))))

  (define (real-part-polar z)
    (* (magnitude-polar z) (cos (angle-polar z))))

  (define (imag-part-polar z)
    (* (magnitude-polar z) (sin (angle-polar z))))

  (define (magnitude-polar z) (car z))

  (define (angle-polar z) (cdr z))

  (define (make-from-real-imag-polar x y)
    (attach-tag 'polar
                (cons (sqrt (+ (square x) (square y)))
                      (atan y x))))

  (define (make-from-mag-ang-polar r a)
    (attach-tag 'polar (cons r a)))

  (define (real-part z)
    (cond ((rectangular? z)
           (real-part-rectangular (contents z)))
          ((polar? z)
           (real-part-polar (contents z)))
          (else (error "Unknown type -- REAL-PART" z))))

  (define (imag-part z)
    (cond ((rectangular? z)
           (imag-part-rectangular (contents z)))
          ((polar? z)
           (imag-part-polar (contents z)))
          (else (error "Unknown type -- IMAG-PART" z))))

  (define (magnitude z)
    (cond ((rectangular? z)
           (magnitude-rectangular (contents z)))
          ((polar? z)
           (magnitude-polar (contents z)))
          (else (error "Unknown type -- MAGNITUDE" z))))

  (define (angle z)
    (cond ((rectangular? z)
           (angle-rectangular (contents z)))
          ((polar? z)
           (angle-polar (contents z)))
          (else (error "Unknown type -- ANGLE" z))))

  (define (add-complex z1 z2)
    (make-from-real-imag (+ (real-part z1) (real-part z2))
                         (+ (imag-part z1) (imag-part z2))))

  (define (make-from-real-imag x y)
    (make-from-real-imag-rectangular x y))

  (define (make-from-mag-ang r a)
    (make-from-mag-ang-polar r a))

Data-Directed Programming and Additivity

  ;; ===================================================================
  ;; 2.4.3: Data-Directed Programming and Additivity
  ;; ===================================================================

  (define (install-rectangular-package)
    ;; internal procedures
    (define (real-part z) (car z))
    (define (imag-part z) (cdr z))
    (define (make-from-real-imag x y) (cons x y))
    (define (magnitude z)
      (sqrt (+ (square (real-part z))
               (square (imag-part z)))))
    (define (angle z)
      (atan (imag-part z) (real-part z)))
    (define (make-from-mag-ang r a)
      (cons (* r (cos a)) (* r (sin a))))

    ;; interface to the rest of the system
    (define (tag x) (attach-tag 'rectangular x))
    (put 'real-part '(rectangular) real-part)
    (put 'imag-part '(rectangular) imag-part)
    (put 'magnitude '(rectangular) magnitude)
    (put 'angle '(rectangular) angle)
    (put 'make-from-real-imag 'rectangular
         (lambda (x y) (tag (make-from-real-imag x y))))
    (put 'make-from-mag-ang 'rectangular
         (lambda (r a) (tag (make-from-mag-ang r a))))
    'done)

  (define (install-polar-package)
    ;; internal procedures
    (define (magnitude z) (car z))
    (define (angle z) (cdr z))
    (define (make-from-mag-ang r a) (cons r a))
    (define (real-part z)
      (* (magnitude z) (cos (angle z))))
    (define (imag-part z)
      (* (magnitude z) (sin (angle z))))
    (define (make-from-real-imag x y)
      (cons (sqrt (+ (square x) (square y)))
            (atan y x)))

    ;; interface to the rest of the system
    (define (tag x) (attach-tag 'polar x))
    (put 'real-part '(polar) real-part)
    (put 'imag-part '(polar) imag-part)
    (put 'magnitude '(polar) magnitude)
    (put 'angle '(polar) angle)
    (put 'make-from-real-imag 'polar
         (lambda (x y) (tag (make-from-real-imag x y))))
    (put 'make-from-mag-ang 'polar
         (lambda (r a) (tag (make-from-mag-ang r a))))
    'done)

  (define (apply-generic op . args)
    (let ((type-tags (map type-tag args)))
      (let ((proc (get op type-tags)))
        (if proc
            (apply proc (map contents args))
            (error
             "No method for these types -- APPLY-GENERIC"
             (list op type-tags))))))

  (define (real-part z) (apply-generic 'real-part z))
  (define (imag-part z) (apply-generic 'imag-part z))
  (define (magnitude z) (apply-generic 'magnitude z))
  (define (angle z) (apply-generic 'angle z))

  (define (make-from-real-imag x y)
    ((get 'make-from-real-imag 'rectangular) x y))

  (define (make-from-mag-ang r a)
    ((get 'make-from-mag-ang 'polar) r a))

Exercise 2.73

Section *Note 2-3-2:: described a program that performs symbolic differentiation:

  (define (deriv exp var)
    (cond ((number? exp) 0)
          ((variable? exp) (if (same-variable? exp var) 1 0))
          ((sum? exp)
           (make-sum (deriv (addend exp) var)
                     (deriv (augend exp) var)))
          ((product? exp)
           (make-sum
             (make-product (multiplier exp)
                           (deriv (multiplicand exp) var))
             (make-product (deriv (multiplier exp) var)
                           (multiplicand exp))))
          <MORE RULES CAN BE ADDED HERE>
          (else (error "unknown expression type -- DERIV" exp))))

We can regard this program as performing a dispatch on the type of the expression to be differentiated. In this situation the "type tag" of the datum is the algebraic operator symbol (such as `+') and the operation being performed is `deriv'. We can transform this program into data-directed style by rewriting the basic derivative procedure as

  (define (deriv exp var)
     (cond ((number? exp) 0)
           ((variable? exp) (if (same-variable? exp var) 1 0))
           (else ((get 'deriv (operator exp)) (operands exp)
                                              var))))

  (define (operator exp) (car exp))

  (define (operands exp) (cdr exp))
  1. Explain what was done above. Why can't we assimilate the predicates `number?' and `same-variable?' into the data-directed dispatch?


    Rather than embed the logic for each operator we want to support in the deriv function, we'll dispatch them based on the operator in the expression.

    number? and same-variable cannot be dispatched this way because they're scalar values, not compound expressions tagged with an operator to dispatch on.

  2. Write the procedures for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above.


      (define (install-deriv-code)
        (define (deriv-sum exp var)
          (make-sum (deriv (addend exp) var)
                    (deriv (augend exp) var)))
        (define (deriv-product expr var)
          (make-sum
           (make-product (multiplier exp)
                         (deriv (multiplicand exp) var))
           (make-product (deriv (multiplier exp) var)
                         (multiplicand exp))))
        (put 'deriv '+ deriv-sum)
        (put 'deriv '* deriv-product))
  3. Choose any additional differentiation rule that you like, such as the one for exponents (*Note Exercise 2-56::), and install it in this data-directed system.
  4. In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together. Suppose, however, we indexed the procedures in the opposite way, so that the dispatch line in `deriv' looked like

      ((get (operator exp) 'deriv) (operands exp) var)

    What corresponding changes to the derivative system are required?


    Nothing, only the implementations of the dispatch table storage / lookup methods ( put / get ) would change.

Exercise 2.74:

Insatiable Enterprises, Inc., is a highly decentralized conglomerate company consisting of a large number of independent divisions located all over the world. The company's computer facilities have just been interconnected by means of a clever network-interfacing scheme that makes the entire network appear to any user to be a single computer. Insatiable's president, in her first attempt to exploit the ability of the network to extract administrative information from division files, is dismayed to discover that, although all the division files have been implemented as data structures in Scheme, the particular data structure used varies from division to division. A meeting of division managers is hastily called to search for a strategy to integrate the files that will satisfy headquarters' needs while preserving the existing autonomy of the divisions.

Show how such a strategy can be implemented with data-directed programming. As an example, suppose that each division's personnel records consist of a single file, which contains a set of records keyed on employees' names. The structure of the set varies from division to division. Furthermore, each employee's record is itself a set (structured differently from division to division) that contains information keyed under identifiers such as `address' and `salary'. In particular:

  1. Implement for headquarters a `get-record' procedure that retrieves a specified employee's record from a specified personnel file. The procedure should be applicable to any division's file. Explain how the individual divisions' files should be structured. In particular, what type information must be supplied?


      (define division-identifier car)
      (define division-data cdr)
      (define tag-division cons)
    
      (define (get-record name tagged-file)
        (let ((division (division-identifier tagged-file))
              (file (division-data tagged-file)))
          (tag-division division (apply-generic 'get-record
                                                (division-identifier file)
                                                name
                                                (division-data file)))))

    Division files must be tagged with a unique identifier for the division.

  2. Implement for headquarters a `get-salary' procedure that returns the salary information from a given employee's record from any division's personnel file. How should the record be structured in order to make this operation work?


      (define (get-record-field tagged-record field)
        (let ((division (division-identifier tagged-record))
              (record (division-data tagged-record)))
          (apply-generic 'get-record-field
                         division
                         record
                         field)))
  3. Implement for headquarters a `find-employee-record' procedure. This should search all the divisions' files for the record of a given employee and return the record. Assume that this procedure takes as arguments an employee's name and a list of all the divisions' files.


      (define (find-employee-record name division-files)
        (let* ((division-file (car division-files))
               (rest (cdr division-files))
               (found-file (get-record name division-file)))
          (if (nil? found-file)
              (find-employee-record name rest)
              found-file)))
  4. When Insatiable takes over a new company, what changes must be made in order to incorporate the new personnel information into the central system?


    The new company's personnel information must be representable in scheme, and will have to be tagged with a new unique identifier. New implementations for get-record and get-record-field will have to be implemented for the new data format.

Message Passing

  ;; ===================================================================
  ;; 2.4.4: Message Passing
  ;; ===================================================================

  (define (make-from-real-imag x y)
    (define (dispatch op)
      (cond ((eq? op 'real-part) x)
            ((eq? op 'imag-part) y)
            ((eq? op 'magnitude)
             (sqrt (+ (square x) (square y))))
            ((eq? op 'angle) (atan y x))
            (else
             (error "Unknown op -- MAKE-FROM-REAL-IMAG" op))))
    dispatch)

  (define (apply-generic op arg) (arg op))

Exercise 2.75

Implement the constructor `make-from-mag-ang' in message-passing style. This procedure should be analogous to the `make-from-real-imag' procedure given above.


  (define (make-from-mag-ang r a)
    (define (dispatch op)
      (cond ((eq? op 'real-part)
             (* r (cos a)))
            ((eq? op 'imag-part)
             (* r (sin a)))
            ((eq? op 'magnitude) r)
            ((eq? op 'angle) a)
            (else
             (error "Unknown op -- MAKE-FROM-MAG-ANG" op))))
    dispatch)

Exercise 2.76

As a large system with generic operations evolves, new types of data objects or new operations may be needed. For each of the three strategiesgeneric operations with explicit dispatch, data-directed style, and message-passing-styledescribe the changes that must be made to a system in order to add new types or new operations. Which organization would be most appropriate for a system in which new types must often be added? Which would be most appropriate for a system in which new operations must often be added?


  • Generic operations with explicit dispatch

    • A new constructor must be built to represent a new data format and uniquely tag it
    • Each generic accessor method must be updated to support a new tagged data format
    • New generics must be written to support all possible data formats

    (Not additive)

  • Data-directed style

    • To add a new format, operations must be registered with a global lookup table using a unique tag
    • To add a new operation, each type implementation must be updated to support the new operation, and a new generic function must be made to dispatch it
  • Message-passing style

    • To add a new type, a new constructor must be built that handles the supported operations
    • To add a new operation, all constructors must be updated to support it

When new types must often be added, data-directed is more appropriate, as people creating new types don't have to worry about the operations contract changing frequently.

When new operations must often be added, message-passing is more appropriate, as operations can be added independently from the type implementations (which can be caught up later).