Generic Operations and Coercion.

Example: Generic Arithmetic System. (A pretty big chunk of code).

Generic operations are a technique for organizing systems; particularly
effective for LARGE systems.

Means of managing complexity through abstraction.

Key ideas are:
  * Define 'abstract methods' that make sense on MANY TYPES of objects
  * Hide the type-specific implementation from the user.

For example, you can ADD:
  - integers
  - rationals
  - floating point numbers
  - complexes
  - polynomials
  - etc.
but the details of how you add them are quite different.

We have seen three ways of implementing generic operations:
  * Generic Functions (Dispatch on Type)  -- for given operation how to do for
                                             each type
  * Message Passing (Dispatch on Operation) -- for given type how to do for
                                               each operation
  * Data Directed (table lookup)

The point is, 
  > Combination of data type and operation name tell you which
    procedure to use.
  > The three different ways were different ways of searching a "table
    of operations":
 
In Dylan (and most languages), use the generic functions style.

----------------------------------------------------------------------

Today we're going to look at a generic arithmetic system that supports
the operations ADD, MUL, SUB, DIV
on many types:
  * integer, rat, float, complex, poly, ...

We are going to use the generic functions style .

----------------------------------------------------------------------

We've already seen implementations of the basic arithmetic operations
for the types <complex> and <rat>.

Dylan has <integer> and <float> numbers built in; we will treat them
both as type <number> for now.


>>> Put this diagram on one side board <<<


    |      ADD     SUB    MUL    DIV      |
    |                                     |
    +----------+--------------+-----------+
    |   <rat>  |  <complex>   | <number>  |
    |          |              |           |
    |  add-rat | add-complex  |  + *      |
    |  mul-rat + mul-complex  +           |
    |          |              |           |
    +----------+------+-------+-----------+

Above an abstraction boundary, we only have access to the advertised
interface for the given type.  For <rat>s this is make-rat, numer,
denom.  For <complex>es it is make-complex-polar,
make-complex-rect, real, imag, mag, ang.

Below a given boundary is the implementation that is free to change.

[Note: it is sometimes useful to have abstraction boundaries BELOW as
 well as above]

So, if we get a call to (ADD A B), we have to figure out who to call:
  * If they're both rationals, then use add-rat, etc.

Do this with generic functions:

(define-generic-function add (x y))
(add-method add (method ((x <number>) (y <number>)) (+ x y)))
(add-method add add-rat)
(add-method add add-complex)

RECALL that the parameter list for add-rat is ((<rat> x) (<rat> y)) and
the parameter list for add-complex is ((<complex> x) (<complex> y)),
so the generic calls the appropriate function based on the types of
the arguments.  

e.g., (add (make-rat 1 2) (make-rat 3 4)) will call add-rat because
both arguments to add are of type <rat>.

Note that if the two arguments are not the same type, then an error
will be generated because there are no functions to handle such cases.

e.g., (add 1 (make-rat 1 2)) will cause an error, no applicable
function, because there is no function for add that matches one
argument of type <number> and one of type <rat>.

We will return to this below.

----------------------------------------------------------------------

There are lots of other things we could do arithmetic on.
Polynomials, for example.

UNIVARIATE polynomials are ones with only one variable:
  a_0 x^0 + a_1 x^1 + ... + a_n x^n

For a given TERM a_jx^j, 
   a_j is the COEFFICIENT
   j   is the ORDER
and that's enough to tell what the term is.
e.g.
  3x^2 has coefficient 3 and order 2.

We can define a term abstraction

(define-class <term> (<object>)
  (term-order <integer>)
  (term-coeff <object>))

[Don't just use <pair>'s because best to have a specific type to identify terms]

The order can only be an integral value.  The coeff, however we
allow to be arbitrary, so that we can support things like <rat>s as
well as ordinary <number>s as coefficients.

We can define basic operations on terms:

(define (add-term <function>)
  (method ((t1 <term>) (t2 <term>))
    (make <term> term-order: (term-order t1)
                             ;;; note use of generic ADD here
                 term-coeff: (add (term-coeff t1) (term-coeff t2)))))

Note the use of generic ADD in add-term.  Why? It will allow us to add
terms that are not simple <number>s.  [Such as everything in the system,
including polynomials!]

Next, we're going to implement polynomials as  TERMLISTS, 
  * ordered lists of terms, lowest order first.

(define <termlist> <list>)

[Note: (define (<termlist> <class>) <list>)]

We can now define operations on <termlist>s

(define (first-term <function>)
  (method ((t <termlist>)) (head t)))

(define (rest-terms <function>)
  (method ((t <termlist>)) (tail t)))

(define empty-termlist? null?)

Note that this is a trivial set of operations, and one might be
tempted to use cons/head/tail directly.  A BAD idea, because then we
would not be free to change the implementation of <termlist>s easily. 

We can add two termlists by:
  * Using add-term to add terms of the same order
  * Just collect the other terms together in the right order

"two finger method"

>> Here's code from the handout... <<

(define (add-terms <function>)
  (method ((l1 <termlist>) (l2 <termlist>))
    (cond ((empty-termlist? l1) l2)
          ((empty-termlist? l2) l1)
          (else:
           (bind (((t1 <term>) (first-term l1))
                  ((t2 <term>) (first-term l2))
                  ((r1 <termlist>) (rest-terms l1))
                  ((r2 <termlist>) (rest-terms l2)))
             (cond ((< (term-order t1) (term-order t2))
                    (adjoin-term t1 (add-terms r1 l2)))
                   ((< (term-order t2) (term-order t1))
                    (adjoin-term t2 (add-terms l1 r2)))
                   (else:
                    (adjoin-term (add-term t1 t2) (add-terms r1 r2)))))))))

(Multiplying, etc. is similar but a bit trickier.)

----------------------------------------------------------------------

Now, we define a polynomial to be a structure including
  * The variable that it is a polynomial over
  * The termlist

(define-class <poly> (<object>)
  (poly-variable <symbol>)
  (poly-terms <termlist>))

We want to be able to check if two polynomials are in the same
variable

(define (same-variable? <function>)
  (method ((p1 <poly>) (p2 <poly>)) 
   (= (poly-variable p1) (poly-variable p2))))


Then, in order to add two *polynomials*, we:
  * Check to make sure they're in the same variable
  * Do add-terms on the termlists
  * Package the answer right.

(define (add-poly <function>)
  (method ((p1 <poly>) (p2 <poly>))
    (if (same-variable? p1 p2)
        (make <poly>
         poly-variable: (poly-variable p1)
         poly-terms: (add-terms (poly-terms p1) (poly-terms p2)))
        (error "Polynomials not in same variable -- ADD-POLY"))))

And we add this function to the generic ADD

(add-method add add-poly)

NOTE: add-method adds a function to a generic.  If you
change the definition of add-poly and you want the new definition in
the generic add, you MUST re-evaluate the add-function. Why? the name
add-poly now refers to something new, but the generic does not refer
to functions by name, but rather by value!

----------------------------------------------------------------------

Notice that we can handle polynomials whose coefficients are any type
that the generic arithmetic system handles, not just Dylan numbers,
but also rationals, complexes and EVEN POLYNOMIALS THEMSELVES.  All of
this results from simply using ADD rather than + in add-term!

We have a recursive type:
   * A polynomial can include a polynomial

Look at the system as a whole:
  * Many Parts
  * Well-specified organization
  * That helps keep them apart.

>>>>>>>>>  add polynomials to the diagram <<<<<<<<<<<<<<

----------------------------------------------------------------------

OK, this works really well ...
  ... as long as we keep the types segregated.

  * If we add <rat> 3/2 to the <number> 2, we get an error.

  * This is really unpleasant.  
    - One of the main points of the generic function ADD is so
      that you don't have to keep track of what type everything is.
    - But in effect you do anyways because you have to ensure things
      are the SAME type! Barf!

We could write new procedures for each operation that handled
combinations:

  * add-rational-to-integer

But this is unpleasant:

  - Now we have operation x type1 x type2, rather than just 
    operation x type

  - All these combinations are a VAST amount of extra work.

BUT: most things that can be added (or multiplied, etc) are at least sort of similar.
  If we're adding 3/2 to 2, we can just
  * Turn 2 into a rational, 2/1
  * Add the rationals via rational arithmetic.

HIERARCHY of conversions: <integer> -> <rat> -> <float> -> <complex>

This kind of type conversion is called COERCION.  Cover more in rectitation.
----------------------------------------------------------------------

GENERIC ARITHMETIC SYSTEM:
  * Generic operations using generic function style
  * Recursive types -- e.g., polynomial
  * Coercion: moving up and down in type hierarchies.