Given the similarities between the precedence list framework described above and elements of CLOS, I feel that it is interesting to go one step further in the direction of CLOS, namely to simulate method combination.
It is the purpose of this (and the following) section to show how it is possible to experiment with an ``advanced topic''. I do not, by any means, intend to deal with all the necessary details of method combination (and an efficient implementation of method combination).
A method in CLOS has associated a role. ``Ordinary methods''
are called primary methods.
In addition, there are before methods, after methods,
and around methods.
The methods of a generic function, say m
, contribute
to the so-called effective method of m
.
Effective methods are activated via the generic function,
much along the lines described in section of this report.
Considering only before methods, primary primary, and after methods, the following rules represent a simple, but useful method combination strategy.
are called in most-specific-first order.
The method definition technique in our simulated object-oriented language must be changed to account for the roles of method. I chose to represent the roles of methods in the dispatch
procedure of the class. The following is an example of a dispatch procedure with a specification of roles.
(define (dispatch message) (cond ((match? message class-id) class-name) ((match? message 'set-self!) set-self) ((match? message '(m before)) m) ((match? message '(n after)) n) (else ())))
In the class which contains dispatch , the method m
is designated as a before method, and n is designated as an after method. Set-self has not been associated a role, and therefore it is considered a primary method. Notice that the introduction of roles is a proper extension of the existing framework, because ``role-less'' methods are allowed (they are considered as primary methods).
The procedure match? has the responsibility to match messages against method selectors, doing the appropriate defaulting. Both messages and selectors may be two element lists with roles.
(define (match? message selector) (cond ((symbol? message) (or (eq? message selector) ;(1) (and (pair? selector) (eq? (cadr selector) 'primary) ;(4) (eq? (car selector) message)))) ((pair? message) (or (equal? message selector) ;(2) (and (symbol? selector) (eq? (cadr message) 'primary) ;(3) (eq? (car message) selector)))) (else (error "Malformed message in match?"))))
In match? there are four cases to consider (marked in the program above):
In Scheme it is not difficult to simulate the aggregation of primary, after, and before methods into an effective method. The following procedure describes the aggregation proposed above.
(define (method-combination object message) (let ((before-methods (lookup-all-methods object message 'before)) (primary-methods (lookup-all-methods object message 'primary)) (after-methods (lookup-all-methods object message 'after))) (if (null? primary-methods) () (lambda pl (for-each (lambda (m) (apply m pl)) before-methods) (let ((result (apply (car primary-methods) pl))) (for-each (lambda (m) (apply m pl)) (reverse after-methods)) result)))))
It is easy to make experiments with other combination rules. This can be done entirely local to the procedure method-combination .
The procedure lookup-all-methods is a ``generalization'' of the procedure method-lookup . Lookup-all-methods returns a list of all methods of a particular role.
(define (lookup-all-methods object-precedence-list message . role) (let ((actual-role (if role (car role) 'primary))) (filter procedure? (map (lambda (object) (method-lookup-single-object object message actual-role)) object-precedence-list)))) (define (method-lookup-single-object object message . role) (if role (object (list message (car role))) (object message)))
If no role
is passed as a parameter to lookup-all-methods
,
the role is defaulted to primary
.
Method-lookup-single-object
is an extension of the similar
procedure from section , which now supports message roles.
Having defined method-combination , it may play the exact same role as method-lookup . I.e., instead of calling method-lookup in send , we now call method-combination .
(define (send object message . args) (let ((effective-method (method-combination object message))) (cond ((procedure? effective-method) (apply effective-method args)) ((null? effective-method) (error "Message not understood: " message)) (else (error "Inappropriate result from method lookup: " effective-method)))))