CS 22
Clab 15: More with Multiple representations
Message Passing and Data-driven genericity
For homework, you played around with
tagged representations and a table based complex number package
that allowed two different representations of complex numbers.
If you don't already have a week5
subdirectory, make one now and when you save today, save into
it.
Here are some values from last clab to help us test our
table driven functions.
;; some numbers in simple rectangular form
> cpx30
(1.7320508075688772 . 1)
> cpx45
(4 . 4)
> cpx60
(2 . 3.4641016151377544)
>
;; same number in simple polar form
> acpx30
(1.9999999999999998 . 0.5235987755982989)
> acpx45
(5.656854249492381 . 0.7853981633974483)
> acpx60
(3.9999999999999996 . 1.0471975511965976)
>
;; a couple of constants of interest
> root3
1.7320508075688772
> pi
3.141592653589793
> pidiv6
0.5235987755982988
> pidiv3
1.0471975511965976
> pidiv2
1.5707963267948966
>
;; some correct arithmetic in simple rectangular form
> (add-complex cpx30 cpx45)
(5.732050807568877 . 5)
> (add-complex cpx30 cpx60)
(3.732050807568877 . 4.464101615137754)
> (mul-complex cpx30 cpx45)
(2.9282032302755088 . 10.928203230275509)
> (mul-complex cpx30 cpx60)
(4.898587196589412e-16 . 7.999999999999998)
>
;; some correct arithmetic in simple polar form
> (add-complex acpx30 acpx45)
(7.606339885947184 . 0.7172920193875758)
> (add-complex acpx30 acpx60)
(5.818625822352818 . 0.8744781864705565)
> (mul-complex acpx30 acpx45)
(11.31370849898476 . 1.3089969389957472)
> (mul-complex acpx30 acpx60)
(7.999999999999998 . 1.5707963267948966)
Last time we implemented the table driven package and tried
the following.
>(define new30 (make-from-real-imag 1.7320508075688772 1))
> (define new45 (make-from-mag-ang 5.656854249492381 0.7853981633974483))
> new30
(rectangular 1.7320508075688772 . 1)
> new45
(polar 5.656854249492381 . 0.7853981633974483)
> (real-part new30)
1.7320508075688772
> (real-part new45)
4.000000000000001
This is looking good so let's do some arithmetic.
> (define prod3045 (mul-complex new30 new45))
reference to undefined identifier: mul-complex
OOPS. What is the problem? What do we need to do?
We had not put in the arithmetic functions. Since we
have appropriate constructors and selectors, we can use the
good old functions we had before.
;; the same implementation independent definitions of
;; add, sub, mult, div
(define (add-complex z1 z2)
(make-from-real-imag (+ (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))
(define (sub-complex z1 z2)
(make-from-real-imag (- (real-part z1) (real-part z2))
(- (imag-part z1) (imag-part z2))))
(define (mul-complex z1 z2)
(make-from-mag-ang (* (magnitude z1) (magnitude z2))
(+ (angle z1) (angle z2))))
(define (div-complex z1 z2)
(make-from-mag-ang (/ (magnitude z1) (magnitude z2))
(- (angle z1) (angle z2))))
Putting these in at the top level, we can get:
> (define new30 (make-from-real-imag 1.7320508075688772 1))
> (define new45 (make-from-mag-ang 5.656854249492381 0.7853981633974483))
> (define prod3045 (mul-complex new30 new45))
> prod3045
(polar 11.31370849898476 . 1.3089969389957472)
>
Now we are going to take a different path to obtain genericity. This
is called message passing. It is at the heart of object-oriented
programming. We create an object which encapsulates data and
operators on that data. In this case we will start with a constructor
for a complex number in rectangular form that encapsulates its
real and imaginary parts and the selectors real-part, imag-part,
magnitude, and angle. We could encapsulate arithmetic operators in
the object, but your authors' choose to use their good old arithmetic
operators from before.
Here is the code from the message passing part of section 2.4.3:
;; 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))
In order to make convenient use of this (say with the good old
arithmetic functions) we'll add some generic selectors:
(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))
We'll need square and the good old
arithmetic functions. Also, let's add a convenient way to
see the value of an object.
(define (square x) (* x x))
;; the same implementation independent definitions of
;; add, sub, mult, div
(define (add-complex z1 z2)
(make-from-real-imag (+ (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))
(define (sub-complex z1 z2)
(make-from-real-imag (- (real-part z1) (real-part z2))
(- (imag-part z1) (imag-part z2))))
(define (mul-complex z1 z2)
(make-from-mag-ang (* (magnitude z1) (magnitude z2))
(+ (angle z1) (angle z2))))
(define (div-complex z1 z2)
(make-from-mag-ang (/ (magnitude z1) (magnitude z2))
(- (angle z1) (angle z2))))
;;since we can't see components easily, make show functions
(define showrect (lambda (z) (cons (real-part z) (imag-part z))))
(define showpolar (lambda (z) (cons (magnitude z) (angle z))))
Save these definitions in a file called msgcpx.scm and execute.
Now try some of the following.
> (define msg45 (make-from-real-imag 4 4))
> (define msg30 (make-from-real-imag (sqrt 3) 1))
> (showrect msg45)
(4 . 4)
> (showrect msg30)
(1.7320508075688772 . 1)
> (showpolar msg30)
(1.9999999999999998 . 0.5235987755982989)
> (showpolar msg45)
(5.656854249492381 . 0.7853981633974483)
> (define add3045 (add-complex msg30 msg45))
> (showrect add3045)
(5.732050807568877 . 5)
>
Looks pretty good. But now try.
(define mul3045 (mul-complex msg30 msg45))
Oops, there are a number of workarounds but the best
is to solve exercise 2.75.
You can also construct
some numbers in polar form. This is one of your homework problems
for next Tuesday.
You should be able to do stuff like:
> (define msg30 (make-from-real-imag (sqrt 3) 1))
> (define msg45 (make-from-real-imag 4 4))
> (define mul3045 (mul-complex msg30 msg45))
> (showrect mul3045)
(2.9282032302755088 . 10.928203230275509)
> (showpolar mul3045)
(11.31370849898476 . 1.3089969389957472)
>
after you finish exercise 2.75 at home.
Numbers, numbers, numbers of various types
Back to table driven stuff, but now we want to make a package
that not only handles complex numbers in two forms but also
scheme numbers and rational numbers as defined in section 2.1.1.
A complete working implementation of the generic
number package from SICP section 2.5.1 pp. 189-192 is
available in my pub directory:
/home/cfk/pub/cs22/week5/numbs2.scm
Import
numbs2.scm to your week5 subdirectory and open it in drscheme.
Press execute and try:
> seven
(scheme-number . 7)
> ten
(scheme-number . 10)
> five
(scheme-number . 5)
> genb30
(complex rectangular 1.7320508075688772 . 1)
> genb45
(complex rectangular 4 . 4)
> gena45
(complex polar 5.656854249492381 . 0.7853981633974483)
> (mul gena45 genb30)
(complex polar 11.31370849898476 . 1.3089969389957472)
> (mul ten five)
(scheme-number . 50)
> pct75
(rational 3 . 4)
> tenr
(rational 10 . 1)
> (mul pct75 tenr)
(rational 15 . 2)
So it appears that we have a working package that can do generic
arithmetic. We'll take a tour of numbs2.scm together.
Now, let's try squaring an ordinary number or one of our
generic numbers.
> ten
(scheme-number . 10)
> (square ten)
reference to undefined identifier: square
Not terribly surprising, we don't have square defined at the top-level.
If we want a generic square, we should define it like we did add, sub, etc.
So let's add
;; let's add a square operator
(define (square x) (apply-generic 'square x))
just after div in our list of generic operators. Now we try again
and get:
> ten
(scheme-number . 10)
> (square ten)
No method for these types -- APPLY-GENERIC (square (scheme-number))
Well, that's not very encouraging. Then again, we didn't put
any square operators in the table in any of the number packages.
So why should apply-generic find any? Let's put some in. Try
the following in the scheme-number package just after (put 'make ...
(put 'square '(scheme-number)
(lambda (x) (tag (* x x))))
Try execute and see if you get:
> ten
(scheme-number . 10)
> (square ten)
(scheme-number . 100)
Looking good. Now all we have to do is put square in
the rational and complex packages. Do it first just for
rationals and test that to see if you
get:
> ten
(scheme-number . 10)
> tenr
(rational 10 . 1)
> pct75
(rational 3 . 4)
> (square ten)
(scheme-number . 100)
> (square tenr)
(rational 100 . 1)
> (square pct75)
(rational 9 . 16)
Ok. It works for scheme-numbers and rationals. Only now should
you go ahead and do for complex. But do that at home or later if
there is time.
Let's turn our attention to ex 2.77.
We can construct Louis' number and try magnitude. We get
> (define louis (make-complex-from-real-imag 3 4))
> louis
(complex rectangular 3 . 4)
> (magnitude louis)
magnitude: expects argument of type ; given (complex rectangular 3 . 4)
>
This isn't even what the text says. The reason is that drscheme
has a magnitude function that is built-in. We need to define
a magnitude function for our generic package that will override
the built-in one. We expect to define our generic magnitude
in a fashion similar to how we did square. So let's add:
;; the new magnitude operator
(define (magnitude x) (apply-generic 'magnitude x))
just after the top-level square we added. Now we get:
> (define louis (make-complex-from-real-imag 3 4))
> louis
(complex rectangular 3 . 4)
> (magnitude louis)
No method for these types -- APPLY-GENERIC (magnitude (complex))
>
This is what the problem in the text says, so now you can do the
problem. :-) You need to decide where to put the code suggested by
Alyssa. Remember that at any given level,
internal definitions have to precede anything
else, so make sure you add this code after any internal definitions
in the complex package. If you get it in the right place you ought
to be able to get:
> (define louis (make-complex-from-real-imag 3 4))
> louis
(complex rectangular 3 . 4)
> (magnitude louis)
5
>
ask any questions you may have.