next up previous
Next: 2. The TOOL Interpreter Up: No Title Previous: No Title

1. Classes, instances, and generic functions

The framework we'll be using in TOOL (which is the same as in many object-oriented systems) includes basically the same ideas as we've already seen, although with different terminology. An object's behavior is defined by its class--the object is said to be an instance of the class. All instances of a class have identical behavior, except for information held in a set of specified slots, which provides the local state for the instance. Following Dylan, we'll use the convention of naming classes with names that are enclosed in angle brackets, for example <account> or <number>.gif

The define-class special form creates a new class. You specify the name of the class, the class's superclass, and the names for the slots. In TOOL, every class has a superclass, whose behavior (and slots) it inherits. There is a predefined class called <object> that is the most general kind of object. Every TOOL class has <object> as an ancestor. Once you have defined a class, you use the special form make to create instances of it. Make takes the class as argument, together with a list that specifies values for the slots. The order in which the slots and values are listed does not matter, since each slot is identified by name. For example, we can specify that a ``cat'' is a kind of object that has a size and a breed, and then create an instance of <cat>. Note the use of the get-slot procedure to obtain the value in a designated slot.

Tool==> (define-class <cat> <object> size breed)
;;; Tool value: (defined class: <cat>)

Tool==> (define garfield (make <cat> (size 6) (breed 'weird)))
;;; Tool value: ok

Tool==> (get-slot garfield 'breed)
;;; Tool value: weird

Tool doesn't have ordinary procedures. Rather, it has generic-functions, which do different things depending upon the classes of their arguments. A generic function is defined using the special form define-generic-function:

Tool==> (define-generic-function 4-legged?)
;;; Tool value: (defined generic function: 4-legged?)

You can think of a newly defined generic function as an empty table to be filled in with methods. You use define-method to specify methods for a generic function that determine its behavior on various classes.

Tool==> (define-method 4-legged? ((x <cat>))
           true)
;;; Tool value: (added method to generic function: 4-legged?)

Tool==> (define-method 4-legged? ((x <object>))
           'Who-knows?)
;;; Tool value: (added method to generic function: 4-legged?)

Tool==> (4-legged? garfield)
;;; Tool value: #t

Tool==> (4-legged? 'Hal)
;;; Tool value: who-knows?

The list in define-method following the generic function name is called the list of specializers for the method. This is like an argument list, except that it also specifies the class of each argument. In the first example above, we define a method for 4-legged? that takes one argument named x, where x is a member of the class <cat>. In the second example, we define another method for 4-legged? that takes one argument named x, where x is a member of the class <object>. Now 4-legged? will return true if the argument is a cat, and will return who-knows? if the argument is an object. Notice that garfield is an object as well as a cat (because <object> is the superclass of <cat>). Yet, when we call 4-legged? with garfield as an input, TOOL uses the method for <cat>, and not the method for <object>. In general, TOOL uses the most specific method that applies to the inputs.gif

In a similar way, we can define a new generic function say and give it a method for cats (and subclasses of cats):

Tool==> (define-generic-function say)
;;; Tool value: (defined generic function: say)

Tool==> (define-method say ((cat <cat>) (stuff <object>))
          (newline)
          (print 'meow) ;print is TOOL's procedure for printing things
          (newline)
          (print stuff)
          'done)
;;; Tool value: (added method to generic function: say)

Tool==> (define-class <house-cat> <cat> address)
;;; Tool value: (defined class: <house-cat>)

Tool==> (define socks                  ;note that a house cat is a cat, and therefore
          (make <house-cat>    ;has slots for breed and size, as well
                (size 'medium) ;as for address
                (address '(1600 Pennsylvania Avenue Washington DC))))
;;; Tool value: ok

Tool==> (get-slot socks 'breed)
;;; Tool value: *undefined*     ;we never initialized Socks's breed

Tool==> (say garfield '(feed me))
meow
(feed me)
;;; Tool value: done

Tool==> (say socks '(feed me))
meow
(feed me)
;;; Tool value: done

Tool==> (say 'hal '(feed me))
;No method found -- APPLY-GENERIC-FUNCTION

In the final example, TOOL signals an error when we apply say to the symbol hal. This is because hal is a symbol (not a cat) and there is no say method defined for symbols.

We can go on to define more subclasses of <cat>:

Tool==> (define-class <show-cat> <cat> awards)
;;; Tool value: (defined class: <show-cat>)

Tool==> (define-method say ((cat <show-cat>) (stuff <object>))
          (newline)
          (print stuff)
          (newline)
          (print '(I am beautiful))
          'done)
;;; Tool value: (added method to generic function: say)

Tool==> (define Cornelius-Silverspoon-the-Third
           (make <show-cat>
                 (size 'large)
                 (breed '(Cornish Rex))
                 (awards '((prettiest skin)))))
;;; Tool value: ok

Tool==> (say cornelius-silverspoon-the-Third '(feed me))
(feed me)
(i am beautiful)
;;; Tool value: done

Tool==> (define-method say ((cat <cat>) (stuff <number>))
          (newline)
          (print '(cats never discuss numbers))
          'done)
;;; Tool value: (added method to generic function: say)

Tool==> (say socks 37)
(cats never discuss numbers)
;;; Tool value: done

As the final example illustrates, TOOL picks the appropriate method for a generic function by examining the classes of all the arguments to which the function is applied. This differs from the message-passing model, where the dispatch is done by a single object.

Notice also that TOOL knows that 37 is a member of the class <number>. In TOOL, every data object is a member of some class. The classes <number>, <symbol>, <pair>, and <procedure> are predefined, with <object> as their superclass. Also, every procedure is a generic procedure, to which you can add new methods. The following generic procedures are predefined, each initially with a single method as indicated by the specializer:

+         (<number> <number>)
-         (<number> <number>)
*         (<number> <number>)
/         (<number> <number>)
=         (<number> <number>)
>         (<number> <number>)
<         (<number> <number>)
sqrt      (<number>)
cons      (<object> <object>)
car       (<pair>)
cdr       (<pair>)
true?     (<object>)
false?    (<object>)
not       (<object>)
null?     (<object>)
print     (<object>)
get-slot  (<object> <symbol>)
set-slot! (<object> <symbol> <object>)
display   (<object>)
newline   ()


next up previous
Next: 2. The TOOL Interpreter Up: No Title Previous: No Title

Hal Abelson
Sat Apr 11 16:28:40 EDT 1998