9.8. Defining Abstract Objects


9.8. Defining Abstract Objects

This example demonstrates a syntactic extension that facilitates the definition of simple abstract objects (see Section 2.9). This facility has unlimited potential as the basis for a complete object-oriented subsystem in Scheme.

Abstract objects are similar to basic data structures such as pairs and vectors. Rather than being manipulated via access and assignment operators, however, abstract objects respond to messages. The valid messages and the actions to be taken for each message are defined by code within the object itself rather than by code outside the object, resulting in more modular and potentially more secure programming systems. The data local to an abstract object is accessible only through the actions performed by the object in response to the messages.

A particular type of abstract object is defined with define-object, which has the general form

 (define-object (name var1 ...) ((var2 val) ...) ((msg action) ...)) 

The first set of bindings ((var2 val) ) may be omitted. define-object defines a procedure that is called to create new abstract objects of the given type. This procedure is called name, and the arguments to this procedure become the values of the local variables var1 . After the procedure is invoked, the variables var2 are bound to the values val in sequence (as with let*) and the messages msg are bound to the procedure values action in a mutually recursive fashion (as with letrec). Within these bindings, the new abstract object is created; this object is the value of the creation procedure.

The syntactic form send-message is used to send messages to abstract objects. (send-message object msg arg ) sends object the message msg with arguments arg . When an object receives a message, the arg become the parameters to the action procedure associated with the message, and the value returned by this procedure is returned by send-message.

The following examples should help to clarify how abstract objects are defined and used. The first example is a simple kons object that is similar to Scheme's built-in pair object type, except that to access or assign its fields requires sending it messages.

 (define-object (kons kar kdr)   ((get-car (lambda () kar))     (get-cdr (lambda () kdr))     (set-car! (lambda (x) (set! kar x)))     (set-cdr! (lambda (x) (set! kdr x))))) (define p (kons 'a 'b)) (send-message p get-car)  a (send-message p get-cdr)  b (send-message p set-cdr! 'c) (send-message p get-cdr)  c 

The simple kons object does nothing but return or assign one of the fields as requested. What makes abstract objects interesting is that they can be used to restrict access or perform additional services. The following version of kons requires that a password be given with any request to assign one of the fields. This password is a parameter to the kons procedure.

 (define-object (kons kar kdr pwd)   ((get-car (lambda () kar))    (get-cdr (lambda () kar))    (set-car!      (lambda (x p)        (if (string=? p pwd)            (set! kar x))))   (set-cdr!     (lambda (x p)       (if (string=? p pwd)           (set! kar x)))))) (define p1 (kons 'a 'b "magnificent")) (send-message p1 set-car! 'c "magnificent") (send-message p1 get-car)  c (send-message p1 set-car! 'd "please") (send-message p1 get-car)  c (define p2 (kons 'x 'y "please")) (send-message p2 set-car! 'z "please") (send-message p2 get-car)  z 

One important ability of an abstract object is that it can keep statistics on messages sent to it. The following version of kons counts accesses to the two fields. This version also demonstrates the use of explicitly initialized local bindings.

 (define-object (kons kar kdr)   ((count 0))   ((get-car     (lambda ()       (set! count (+ count 1))       kar))    (get-cdr      (lambda ()        (set! count (+ count 1))        kdr))      (accesses        (lambda () count)))) (define p (kons 'a 'b)) (send-message p get-car)  a (send-message p get-cdr)  b (send-message p accesses)  2 (send-message p get-cdr)  b (send-message p accesses)  3 

The implementation of define-object is straightforward. The object definition is transformed into a definition of the object creation procedure. This procedure is the value of a lambda expression whose arguments are those specified in the definition. The body of the lambda consists of a let* expression to bind the local variables and a letrec expression to bind the message names to the action procedures. The body of the letrec is another lambda expression whose value represents the new object. The body of this lambda expression compares the messages passed in with the expected messages using a case expression and applies the corresponding action procedure to the remaining arguments.

For example, the definition

 (define-object (kons kar kdr)   ((count 0))   ((get-car     (lambda ()       (set! count (+ count 1))       kar))     (get-cdr      (lambda ()        (set! count (+ count 1))        kdr))      (accesses        (lambda () count)))) 

is transformed into

 (define kons   (lambda (kar kdr)     (let* ((count 0))       (letrec ((get-car                 (lambda ()                   (set! count (+ count 1)) kar))                (get-cdr                 (lambda ()                   (set! count (+ count 1)) kdr))                (accesses (lambda () count)))       (lambda (msg . args)         (case msg           ((get-car) (apply get-car args))           ((get-cdr) (apply get-cdr args))           ((accesses) (apply accesses args))           (else            (error 'kons "invalid message ~s"               (cons msg args))))))))) -------------------------------------------------------------------------- ;;; define-object creates an object constructor that uses let* to bind ;;;local fields and letrec to define the exported procedures. An ;;; object is itself a procedure that accepts messages corresponding ;;;to the names of the exported procedures. The second pattern is ;;;used to allow the set of local fields to be omitted. (define-syntax define-object   (syntax-rules ()     (( (name . varlist)        ((var1 val1) ...)        ((var2 val2) ...))     (define name       (lambda varlist         (let* ((var1 val1) ...)           (letrec ((var2 val2) ...)             (lambda (msg . args)               (case msg                 ((var2) (apply var2 args)) ...                 (else                  (error 'name "invalid message ~s"                     (cons msg args))))))))))   (( (name . varlist)      ((var2 val2) ...))    (define-object (name . varlist)      ()      ((var2 val2) ...))))) ;;; send-message abstracts the act of sending a message from the act ;;;of applying a procedure and allows the message to be unquoted. (define-syntax send-message   (syntax-rules ()     (( obj msg arg ...)       (obj 'msg arg ...)))) 

Exercise 9.8.1.

start example

Use define-object to define the stack object type from Section 2.9.

end example

Exercise 9.8.2.

start example

Use define-object to define a queue object type with operations similar to those described in Section 2.9.

end example

Exercise 9.8.3.

start example

It is often useful to describe one object in terms of another. For example, the second kons object type could be described as the same as the first but with a password argument and different actions associated with the set-car! and set-cdr! messages. This is called inheritance; the new type of object is said to inherit attributes from the first. Modify define-object to support inheritance by allowing the optional declaration (inherit object-name) to appear after the message/action pairs. This will require saving some information about each object definition for possible use in subsequent object definitions. Conicting argument names should be disallowed, but other conicts should be resolved by using the initialization or action specified in the new object definition.

end example

Exercise 9.8.4.

start example

Using the definition of method on page 205, define a complete vector-based object system. If your implementation of Scheme supports the definition of opaque records, use records instead of vectors to represent instances for greater security and efficiency. If done well, the resulting object system should be more efficient and easier to use than the system given above.

end example




The Scheme Programming Language
The Scheme Programming Language
ISBN: 026251298X
EAN: 2147483647
Year: 2003
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net