Note: PS #2 is due Thursday, September 23, 4PM. Today: Type dispatching ======================= Manipulating symbolic expressions Example: symbolic differentiation The basic tool: type dispatching. Suppose we want to write a function that computes something slightly different depending on the type of its argument. EXAMPLE: one-less (x) returns x-1 if x is a , returns the tail of x if x is a , otherwise returns x (define (one-less x) (cond ((number? x) (- x 1)) ((list? x) (cdr x)) (else x))) This is known as a dispatch on type, because for each type of argument, a different action is taken. We will see next time that Swindle has a built in mechanism for doing type dispatching, known as generic functions, but for today we will explicitly do the type dispatch rather than using the generic function mechanism (just to show you, there is no magic in generic functions). Symbolic differentiator. [Code will be on the Web.] Rules for taking derivatives: d -- c = 0 dx d -- x = 1 dx d -- v = 0 for v an independent variable dx d du dv -- (u+v) = -- + -- dx dx dx d dv dv -- (u*v) = u * -- + v * -- dx dx dx d n n-1 du -- u = n u -- for n>0 dx dx We are going to manipulate *symbolic* *expressions* using these rules. For example: d -- ax^2 + bx = 2ax + b dx ax^2+b^x is a symbolic expression -- it's not a function -- it's a data structure with symbols (a,b,x) in it. Symbolic differentiation is: -- Exact -- Gives formulas as results. Numerical differentiation (which we saw when we did higher-order functions) is: -- Approximate -- Gives numbers as results. We're going to program the six rules above. * This is one of Lisp's original successes: Macsyma and now Mathematica are common tools for scientists. We are going to restrict ourselves to *binary* operators, just two arguments: (+ A B), (* A B), (^ A B) -- exponentiation. So, we'll write `a + b + c' as `(+ a (+ b c))'. No loss of power. We will represent expressions in the usual Scheme prefix style. 2ax ==> (* 2 (* a x)) [Can you draw a Box&Pointer diagram for this?] ax^2+bx ==> (+ (* a (^ x 2)) (* b x)) [Maybe draw an expression tree?] Our Data Abstraction: We're going to have the following TYPES of expressions: [CONSTANTS]: Type: Predicate: const? represented concretely as s -- this costs us flexibility, no abstraction, but means we can directly use Scheme arithmetic ops. [VARIABLES]: Type: Predicate: var? (same-var? v1 v2) represented concretely as s -- same comment applies. [SUM]: Type: Predicate: sum? Accessors: sum-addend, sum-augend Constructor: make-sum Contract: If x = (make-sum a b) then (sum-addend x) + (sum-augend x) = a + b Note: this contract allows sums to be manipulated as long as maintain mathematical equality to summation. [PRODUCT]: Type: Predicate: prod? Accessors: prod-multiplier, prod-multiplicand Constructor: make-prod Contract: If x = (make-prod a b) then (prod-multiplier x) * (prod-multiplicand x) = a*b [EXPONENT]: (^ a b) Type: Predicate: expt? Accessors: expt-base, expt-power Constructor: make-expt Contract: If x = (make-expt a b) then (expt-base x) ^ (expt-power x) = a^b The derivative function takes: > Expression to be differentiated > Variable to differentiate wrt. (dd '(+ x 3) 'x) means d -- x + 3 dx DD converts from list form into the correct types of expressions (just defined above) and then calls deriv. (define (dd e (v )) (e->l (deriv (l->e e) v))) >>> NEW: can use types for function arguments, if a type is not specified, it defaults to which is the mother-of-all-types. (l->e e) creates the internal datastructures we use (turns a list into a constant, variable, sum, product or expt). So (l->e '(+ x y)) calls (make-sum 'x 'y) [note the quotes!] (e->l x) goes the other way. (e->l (make-sum 'x 'y)) = (list '+ 'x 'y) Note that the variable v does not have to be l->e'ed since we decided to use s (might be cleaner to use an abstraction for that as well). Deriv does a DISPATCH on the TYPE of expression * Dispatch to the code that does the right thing for each specific type of expression, constant, variable, sum, product, expt. (define (deriv e (v )) (cond ((const? e) 0) ((var? e) (if (same-var? e v) 1 0)) ((sum? e) (make-sum (deriv (sum-addend e) v) (deriv (sum-augend e) v))) ((prod? e) (make-sum (make-prod (prod-multiplier e) (deriv (prod-multiplicand e) v)) (make-prod (deriv (prod-multiplier e) v) (prod-multiplicand e)))) ((expt? e) (make-prod (make-prod (expt-power e) (make-expt (expt-base e) (make-sum (expt-power e) -1))) (deriv (expt-base e) v))))) Take a look at how one of the underlying types is implemented - using Swindle's object system: (defstruct (addend ) (augend )) New special form: defstruct, it defines as a new type, a constructor function make-sum, a predicate function sum? and two accessor functions sum-addend and sum-augend. (defstruct SLOT1 ... SLOTn) defines a new STRUCTURE where SLOTi is either (SLOTNAME TYPE) or just SLOTNAME (taking as the type) Note: can actually be ( ) where is another structure to inherit from. Everything that is related to the object system is completely Swindle-specific, you should look for documentation only in the Swindle quick reference or in the on-line documentation of Swindle. We will later see that defstruct is actually a short form for the much more generic defclass form. (make-NAME V1 ... Vn) creates an INSTANCE of the named STRUCTURE (NAME? VAL) tests whether VAL is an instance of the given structure. (NAME-SLOTi VAL) returns the value of the named SLOTi for the given instance Note that since we are using a well-defined interface to our objects, we could implement the whole thing using lists, for example: (define (make-sum x y) (list '+ x y)) (define (sum? x) (and (list? x) (= 3 (length x)) (eq? '+ (first x)))) (define (sum-addend x) (if (sum? x) (second x) (error ...))) (define (sum-augend x) (if (sum? x) (third x) (error ...))) and even wilder implementations are possible (like 2^x*3^y). The moment of truth is... try it out! (dd '(* x 2) 'x) ==> (+ (* x 0) (* 1 2)) Ugh. Gross. But correct at least. We want to *simplify* the result, to get just 2 as an answer. To do this, we just change the constructors to do arithmetic simplification: (make-sum a b): - If a or b is zero just return the other one - If a and b are both numbers then add them as numbers. (make-product a b): - If a=0 or b=0 then 0 - If a=1 or b=1 then return the other. - If both are numbers, multiply them by hand. (make-expt a b): - If a=0 or a=1 then a - If b=0 then 1 - If b=1 then a - If both are numbers, raise them by hand. Note: that these changes still meet the above contracts, because they allow any manipulation that preserves equality to the original sum, product or expt. Let's take a look at the code for the changed make-sum: (define (make-sum x y) (cond ((and (const? x) (const? y)) (+ x y)) ((and (const? x) (= x 0)) y) ((and (const? y) (= y 0)) x) (else (make :addend x :augend y)))) New function - a general constructor: (make :SLOT1 V1 ... :SLOTn Vn) returns an instance of with each SLOTi having Vi as its value. NAME should only be a name of a user-defined class - (make ) will not work. This is something that defstruct is usually doing for us automatically, but here we wanted to define our own constructor so we used this function. Now, we get: (dd '(* x 2) 'x) ==> 2 (dd '(+ (* a (^ x 2)) (* b x)) 'x) ==> (+ (* a (* 2 x)) b) This is amazingly little code for computing derivatives! Suppose we want to get ``better'' output, using infix form: (a + b) instead of (+ a b) Trick: Just change the routines that convert between printed format (i.e., list structure) and internal representation (i.e., const/var/prod/etc). Just e->l and l->e! Changing a few lines of code will do it... because we built the system to be modular! The "printed" and "internal" representations are completely distinct. Change to e->l - instead of things like: (cond ... ((sum? x) (list '+ (e->l (sum-addend x)) (e->l (sum-augend x)))) ...) Use this: (cond ... ((sum? x) (list (e->l (sum-addend x)) '+ (e->l (sum-augend x)))) ...) Also it is easy to change l-> accordingly - simply take these things: ((eq? (second x) '*) (make-prod (l->e (first x)) (l->e (third x)))) and switch the second and first calls! Then, (dd '((a * (x ^ 2)) + (b * x)) 'x) ==> ((a * (2 * x)) + b) That's it! deriv itself doesn't need to change, one bit! Notice that we never said that (make-sum A B) had to be of type sum; we just insisted that the data structure (make-sum A B) corresponds to the mathematical sum of A and B. This allows, e.g. (make-sum 'x 'x) to return (make-prod 2 'x) because they both correspond to the polynomial 2x. In general, it allows all our simplifications. (Analogous to reducing fractions to lowest terms.) Now, let's look at proving that this procedure works correctly: SHOW: (deriv e v) = de/dv by induction on ... depth of expressions. BASE CASE: depth of e is zero * e is a number (deriv e v) = 0 = de/dv * e is a variable: * e is v (deriv e v) = 1 = de/dv * e is some other variable, (deriv e v) = 0 = de/dv [all of these were by substitution.] INDUCTION CASE: ASSUME (deriv e v) = de/dv for any expression e of depth <= n. SHOW (deriv f v) = df/dv for f of depth n+1 There are three cases (1) f is the sum of f1 and f2 (deriv f v) = (make-sum (deriv (sum-addend f) v) (deriv (sum-augend f) v)) [subst.] = (make-sum (deriv f1 v) (deriv f2 v))[contract] = (make-sum df1/dv df2/dv) [by IH] = df1/dv + df2/dv [by rules for sums] = df/dv (2) f is the product of f1 and f2 ... similar ... (3) f is f1 to the f2, where f2 is a number >= 0 ... similar ... We've used relatively simple structures: structures with two slots; And mostly very simple procedrues; To perform fairly complex tasks (differentiation). Structuring the problem was very important: * Good abstractions * Good decomposition >>> Notice how easy it was to make fairly major changes - Add simplification - Hack representation to change to infix. >>> It might have looked like the bottom layer of the abstraction was a lot of extra work for not much benefit. - We knew what the representation was - Most of the functions were trivial. >>> But, having that layer of abstraction let us make some drastic changes underneath it without changing the things built on top of it. It'd be quite easy to write five times as much code that does the same thing. Please don't!