| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
4.5.1 Generic functions and methods 4.5.2 Next-method 4.5.3 Example
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Neither GOOPS nor CLOS use the message mechanism for methods as most Object Oriented language do. Instead, they use the notion of generic functions. A generic function can be seen as a methods "tanker". When the evaluator requested the application of a generic function, all the methods of this generic function will be grabbed and the most specific among them will be applied. We say that a method M is more specific than a method M' if the class of its parameters are more specific than the M' ones. To be more precise, when a generic function must be "called" the system will:
The definition of a generic function is done with the
define-generic macro. Definition of a new method is done with the
define-method macro. Note that define-method automatically
defines the generic function if it has not been defined
before. Consequently, most of the time, the define-generic needs
not be used.
Consider the following definitions:
(define-generic G) (define-method (G (a <integer>) b) 'integer) (define-method (G (a <real>) b) 'real) (define-method (G a b) 'top) |
The define-generic call defines G as a generic
function. Note that the signature of the generic function is not given
upon definition, contrarily to CLOS. This will permit methods with
different signatures for a given generic function, as we shall see
later. The three next lines define methods for the G generic
function. Each method uses a sequence of parameter specializers
that specify when the given method is applicable. A specializer permits
to indicate the class a parameter must belong to (directly or
indirectly) to be applicable. If no specializer is given, the system
defaults it to <top>. Thus, the first method definition is
equivalent to
(define-method (G (a <integer>) (b <top>)) 'integer) |
Now, let us look at some possible calls to generic function G:
(G 2 3) => integer (G 2 #t) => integer (G 1.2 'a) => real (G #t #f) => top (G 1 2 3) => error (since no method exists for 3 parameters) |
The preceding methods use only one specializer per parameter list. Of course, each parameter can use a specializer. In this case, the parameter list is scanned from left to right to determine the applicability of a method. Suppose we declare now
(define-method (G (a <integer>) (b <number>)) 'integer-number) (define-method (G (a <integer>) (b <real>)) 'integer-real) (define-method (G (a <integer>) (b <integer>)) 'integer-integer) (define-method (G a (b <number>)) 'top-number) |
In this case,
(G 1 2) => integer-integer (G 1 1.0) => integer-real (G 1 #t) => integer (G 'a 1) => top-number |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
When a generic function is called, the list of applicable methods is
built. As mentioned before, the most specific method of this list is
applied (see 4.5.1 Generic functions and methods). This method may call
the next method in the list of applicable methods. This is done by using
the special form next-method. Consider the following definitions
(define-method (Test (a <integer>)) (cons 'integer (next-method))) (define-method (Test (a <number>)) (cons 'number (next-method))) (define-method (Test a) (list 'top)) |
With those definitions,
(Test 1) => (integer number top) (Test 1.0) => (number top) (Test #t) => (top) |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
In this section we shall continue to define operations on the <complex>
class defined in Figure 2. Suppose that we want to use it to implement
complex numbers completely. For instance a definition for the addition of
two complexes could be
(define-method (new-+ (a <complex>) (b <complex>))
(make-rectangular (+ (real-part a) (real-part b))
(+ (imag-part a) (imag-part b))))
|
To be sure that the + used in the method new-+ is the standard
addition we can do:
(define-generic new-+)
(let ((+ +))
(define-method (new-+ (a <complex>) (b <complex>))
(make-rectangular (+ (real-part a) (real-part b))
(+ (imag-part a) (imag-part b)))))
|
The define-generic ensures here that new-+ will be defined
in the global environment. Once this is done, we can add methods to the
generic function new-+ which make a closure on the +
symbol. A complete writing of the new-+ methods is shown in
Figure 3.
+ for dealing with complex numbers
|
We use here the fact that generic function are not obliged to have the
same number of parameters, contrarily to CLOS. The four first methods
implement the dyadic addition. The fifth method says that the addition
of a single element is this element itself. The sixth method says that
using the addition with no parameter always return 0. The last method
takes an arbitrary number of parameters(5). This method acts as a kind
of reduce: it calls the dyadic addition on the car of the
list and on the result of applying it on its rest. To finish, the
set! permits to redefine the + symbol to our extended
addition.
To terminate our implementation (integration?) of complex numbers, we can redefine standard Scheme predicates in the following manner:
(define-method (complex? c <complex>) #t) (define-method (complex? c) #f) (define-method (number? n <number>) #t) (define-method (number? n) #f) ... ... |
Standard primitives in which complex numbers are involved could also be redefined in the same manner.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |