[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Standardizing the macroexpansion of make-method-call



Gregor pointed out that in order to be able to analyze effective method
forms, which is something we claim can be done, it is necessary to be
able to recognize the macroexpansion of MAKE-METHOD-CALL.  This is true.
This message is a fairly long discussion of the issues.  You needn't
include all of it in replies.

I see three possible approaches:

(1) Leave MAKE-METHOD-CALL the way it is and add a new function
METHOD-CALL-P.  Given a form this returns the method it calls, or NIL if
the form is not an invocation of a method.  METHOD-CALL-P returns a
second value which is an ordered list of the methods reachable by
CALL-NEXT-METHOD when this method is called.  If the first value is NIL
the second value is also NIL.

(2) Add a macro METHOD-CALL, which only expands correctly in the same
special environment that MAKE-METHOD-CALL is documented to depend on.
Define MAKE-METHOD-CALL to expand into METHOD-CALL.  The syntax of
METHOD-CALL is (METHOD-CALL method [:NEXT-METHODS (next-method*)]), i.e.
if the :NEXT-METHODS option is present, it is a possibly empty ordered
list of methods reachable by CALL-NEXT-METHOD.  If the :NEXT-METHODS
option is absent, CALL-NEXT-METHOD is not available in the method.

(3) Add a macro METHOD-CALL, which only expands correctly in the same
special environment that MAKE-METHOD-CALL is documented to depend on.
Get rid of MAKE-METHOD-CALL and require writers of method combination
procedures to generate METHOD-CALL forms directly.

The advantage of approach 1 is that the structure of a method invocation
form is completely up to the implementation.  Also, the interpretation
of an effective method form does not have to be dependent on an
environment.  By the way, this is what Flavors does, thus this is a
use-tested approach.

The advantage of approaches 2 and 3 is that MAKE-METHOD-CALL's special
dynamic environment could be eliminated, in favor of wrapping a MACROLET
of METHOD-CALL to an implementation-dependent definition around the
effective method form, when actually compiling it.

The advantage of approach 3 is that it leaves CLOS the simplest.

The disadvantage of approach 3 is that MAKE-METHOD-CALL provides a bunch
of convenience features that would now have to be done by hand.  Approach
2 solves this at the cost of some evident redundancy.

None of this deals with the fact that a thing reachable by CALL-NEXT-METHOD
can be not only a regular method, but a grouping of before, primary, and
after methods; currently this works by MAKE-METHOD-CALL accepting forms in
place of methods and somehow translating them.  In Flavors no formal method
is required for this, but it sounds like CLOS is going to need an actual
method object of some new class?  Or can we get away with just returning a
form as an element of the second value of METHOD-CALL-P (approach 1) or
including a form as an element of the :NEXT-METHODS option (approaches 2&3)?
We need to figure out the answers to those questions before we can proceed.

Flavors used to use something like approach 3, and switched to approach 1
to speed up method combination.  I haven't been able to reconstruct the
full details of what happened (it was years ago), but if the efficiency
concern there was not relevant to CLOS, I would prefer approach 2.  To
judge your opinion on approach 3, here are the examples from 2-34 through
2-36 rewritten in approach 3; consult your hardcopy for the originals.
This is assuming we can just put a form where a method object belongs.

;;; Examples of the short form of define-method-combination

(define-method-combination and :identity-with-one-argument t) 

(defmethod func and ((x class1) y) ...)

;;; The equivalent of this example in the long form is:

(define-method-combination and 
        (&optional (order ':most-specific-first))
        ((around (:around))
         (primary (and) :order order :required t))
  (if around
      `(method-call ,(first around)
                    :next-methods
                      `(,@(rest around)
                        ,(if (rest primary)
                             `(and ,@(mapcar #'(lambda (p)
                                                 `(method-call ,p))
                                             primary))
                             (first primary))))
      (if (rest primary)
          `(and ,@(mapcar #'(lambda (p)
                              `(method-call ,p))
                          primary))
          (method-call ,(first primary)))))

;;; Examples of the long form of define-method-combination

;The default method-combination technique
(define-method-combination standard ()
        ((around (:around))
         (before (:before))
         (primary () :required t)
         (after (:after)))
  (if around
      `(method-call ,(first around)
                    :next-methods
                      `(,@(rest around)
                        (multiple-value-prog1
                          (progn
                            ,@(mapcar #'(lambda (b)
                                          `(method-call ,b))
                                      before)
                            ,(if (rest primary)
                                 `(and ,@(mapcar #'(lambda (p)
                                                     `(method-call ,p))
                                                 primary))
                                 (first primary)))
                          ,@(mapcar #'(lambda (a)
                                        `(method-call ,a))
                                    (reverse after)))))
        `(multiple-value-prog1
           (progn
             ,@(mapcar #'(lambda (b)
                           `(method-call ,b))
                       before)
             ,(if (rest primary)
                  `(and ,@(mapcar #'(lambda (p)
                                      `(method-call ,p))
                                  primary))
                  (first primary)))
           ,@(mapcar #'(lambda (a)
                         `(method-call ,a))
                     (reverse after)))))
 
[several examples skipped]

;Order methods by positive integer qualifiers
;:around methods are disallowed to keep the example small
(define-method-combination example-method-combination ()
        ((methods positive-integer-qualifier-p))
  `(progn ,@(mapcar #'(lambda (m) `(method-call ,m))
                    (stable-sort methods #'<
                                 :key #'(lambda (method)
                                          (first (method-qualifiers
                                                   method)))))))

(defun positive-integer-qualifier-p (method-qualifiers)
  (and (= (list-length method-qualifiers) 1)
       (typep (first method-qualifiers) '(integer 0 *))))