From: Daniel Trstenjak
Subject: Problem with Macro expanding to defmethod
Date: 
Message-ID: <20060427180228.GC1618@aludra.science-computing.de>
Hi all,

I tried to build an abstraction for methods.
Each method should have as first expression
a call to raise-method, which gets the
name of the method and the arguments of the
method call.


(defmacro def-fw-method (name (&rest params) &rest body)
   `(defmethod ,name (,@params)
       (raise-method ',name ',params)
       ,@body))


The problem in this version is the invalid usage
of params, because params is the specialized-lambda-list
for defmethod and is not representing the arguments of
the method call.


(defmacro def-fw-method (name (&rest params) &rest body)
   `(defmethod ,name (,@params)
       (raise-method ',name (list ,@params))
       ,@body))


This version will work for something like:

(def-fw-method foo (x y z)
   ...)

But not for something like:

(def-fw-method foo (&rest params)
   ...)


So I have to do some work on params during macro
expansion time. What is the best/correct way to
extract this information from the specialized-lambda-list ?


Thanks for any advice.


Daniel






   
   
 

From: Pascal Costanza
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <4bck8dF115q33U1@individual.net>
Daniel Trstenjak wrote:
> Hi all,
> 
> I tried to build an abstraction for methods.
> Each method should have as first expression
> a call to raise-method, which gets the
> name of the method and the arguments of the
> method call.
> 
> 
> (defmacro def-fw-method (name (&rest params) &rest body)
>    `(defmethod ,name (,@params)
>        (raise-method ',name ',params)
>        ,@body))
> 
> 
> The problem in this version is the invalid usage
> of params, because params is the specialized-lambda-list
> for defmethod and is not representing the arguments of
> the method call.
[...]

> So I have to do some work on params during macro
> expansion time. What is the best/correct way to
> extract this information from the specialized-lambda-list ?

Two questions:

a) Do you really want to call raise-method for each method that is 
called? In case several methods are applicable for some arguments, it 
may then happen that raise-method is called several times.

b) Do you have the CLOS MOP available in your preferred Common Lisp 
implementation?

If the answer to a is no and to b is yes, the simplest solution is this:

(defclass raising-generic-function (standard-generic-function)
   ())

(defmethod compute-discriminating-function
   ((gf raising-generic-function))
   (let ((discriminating-function (call-next-method)))
     (lambda (&rest args)
       (declare (dynamic-extent args))
       (raise-method (generic-function-name gf) args)
       (apply generic-function args))))

The discriminating function is the code that performs the actual method 
dispatch, i.e., selection, combination and application of methods. In 
other words, you can intercept the method dispatch before it is actually 
performed. The arguments to the discriminating function are all the 
arguments that are passed to the generic function.

However, this may not work in some MOP implementations. If it doesn't 
work, try this:

(defmethod compute-discriminating-function
   ((gf raising-generic-function))
   (let ((discriminating-function (call-next-method)))
     (compile nil `(lambda (&rest args)
                     (declare (dynamic-extent args))
                     (raise-gf ',(generic-function-name gf) args)
                     (apply ,discriminating-function args)))))

It seems that some MOP implementations don't like to see "real" closures 
(i.e., function objects that actually close over something) here.


Once you have this generic function class defined, you can define your 
generic function like this:

(defgeneric foo (x y z)
   (:generic-function-class raising-generic-function))

...and the methods can be defined with plain defmethod forms.


If the answer to a is yes and/or the answer to b is no, you can still 
achieve similar effects via a user-defined method combination. Here is a 
method combination that should do what you want in case the answer to a 
is no:

(define-method-combination raising ()
   ((around (:around))
    (before (:before))
    (primary () :required t)
    (after (:after)))
   (:arguments &whole args)
   (:generic-function gf)
   (flet ((call-methods (methods)
            (mapcar #'(lambda (method)
                        `(call-method ,method))
                    methods)))
     (let* ((form (if (or before after (rest primary))
                    `(multiple-value-prog1
                         (progn ,@(call-methods before)
                           (call-method ,(first primary)
                                        ,(rest primary)))
                       ,@(call-methods (reverse after)))
                    `(call-method ,(first primary))))
            (combined (if around
                        `(call-method ,(first around)
                                      (,@(rest around)
                                       (make-method ,form)))
                        form)))
       `(progn
          (raise-gf ,gf ,args)
          ,combined))))

Then you can define your generic function like this:

(defgeneric foo (x y z)
   (:method-combination raising))

...and again define your methods with plain defmethod forms. 
(Unfortunately, this method combination doesn't work in most CLOS 
implementations, although I am pretty sure that this is conforming code. 
Only CMUCL and SBCL do what's expected. Allegro, LispWorks and MCL don't 
process the &whole keyword correctly, and clisp doesn't provide the 
generic function object as expected.)

If the answer to question a is yes, that is you want to call 
raise-method for each method, then you can tweak the method combination 
to do so, or you can use the CLOS MOP to modify 
compute-applicable-methods, compute-effective-method or make-method-lambda.


Pascal

-- 
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
From: Kalle Olavi Niemitalo
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <877j5aepp0.fsf@Astalo.kon.iki.fi>
Pascal Costanza <··@p-cos.net> writes:

> If the answer to question a is yes, that is you want to call
> raise-method for each method, then you can tweak the method
> combination to do so,

How can a method combination type access the arguments passed to
CALL-NEXT-METHOD?
From: Pascal Costanza
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <4bcqd9F10u3bsU2@individual.net>
Kalle Olavi Niemitalo wrote:
> Pascal Costanza <··@p-cos.net> writes:
> 
>> If the answer to question a is yes, that is you want to call
>> raise-method for each method, then you can tweak the method
>> combination to do so,
> 
> How can a method combination type access the arguments passed to
> CALL-NEXT-METHOD?

A right, it can't.


Pascal

-- 
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
From: Daniel Trstenjak
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <20060501101316.GA7450@linux>
> Two questions:
> 
> a) Do you really want to call raise-method for each method that is called? In case several methods are 
> applicable for some arguments, it may then happen that raise-method is called several times.
> 
> b) Do you have the CLOS MOP available in your preferred Common Lisp implementation?

The whole thing was part of a small company internal lecture. The def-fw-method
was a conversion of some c++ code. The CLOS MOP would have been a bit too much.

It's always nice to learn more about CLOS and MOP. Thank you for the nice explanation.


Daniel
From: Kalle Olavi Niemitalo
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <87bqumeq60.fsf@Astalo.kon.iki.fi>
Daniel Trstenjak <················@science-computing.de> writes:

> So I have to do some work on params during macro
> expansion time. What is the best/correct way to
> extract this information from the specialized-lambda-list ?

If I understand correctly, you want a macro to expand to a
DEFMETHOD form whose body first calls your RAISE-METHOD function,
passing the argument list of the method to it.  This argument
list may be what was passed to the generic function, or it may
have been replaced with CALL-NEXT-METHOD.

Consider this case:

  (def-fw-method foo ((a symbol) &optional c (d 42 d-p) &key e)
    (list a c d d-p e))

You cannot get the argument list just by examining the parameters
(A C D D-P E), especially if :allow-other-keys was involved.  So I
don't think you can pass that lambda list directly to DEFMETHOD.
The simplest solution would be to generate code like this:

  (defmethod foo ((a symbol) &rest #1=#:args)
    (apply #'raise-method 'foo a #1#)
    (destructuring-bind (&optional c (d 42 d-p) &key e) #1#
      (list a c d d-p e)))

Unfortunately, the modified lambda list would not be congruent
with the original one, and the user would then have to worry
about that when writing standard DEFGENERIC or DEFMETHOD forms
for the same generic function.  So, the expansion needs to be
more complex:

  (defmethod foo ((a symbol)
                  &optional (c nil #1=#:c-p) (d 42 d-p)
                  &rest #2=#:args &key e)
    (apply #'raise-method 'foo a
           (nconc (when #1# (list c))
                  (when d-p (list d))
                  #2#))
    (list a c d d-p e))

I think the code transforming the lambda list should signal an
error if the lambda list includes any unrecognized symbols that
are listed in LAMBDA-LIST-KEYWORDS.

And then, if the body of the DEF-FW-METHOD form begins with bound
declarations, you'll have to move those above the RAISE-METHOD call.

All this mess could perhaps be avoided with the MOP.  I recall it
generates a "method function" for each method, and the method
function receives the whole argument list as well as a list of
next methods.  So if you could hook up into how that function is
generated, you could snatch the argument list before it is
chopped up into separate parameters.  I don't know how this might
best be accomplished in practice.
From: Pascal Costanza
Subject: Re: Problem with Macro expanding to defmethod
Date: 
Message-ID: <4bcqnkF117qniU1@individual.net>
Kalle Olavi Niemitalo wrote:

> All this mess could perhaps be avoided with the MOP.  I recall it
> generates a "method function" for each method, and the method
> function receives the whole argument list as well as a list of
> next methods.  So if you could hook up into how that function is
> generated, you could snatch the argument list before it is
> chopped up into separate parameters.  I don't know how this might
> best be accomplished in practice.

You could implement a method class, roughly like this:

(defclass raising-method (standard-method)
   ())

(defmethod initialize-instance :around
   ((method raising-method) &rest initargs &key function)
   (declare (dynamic-extent initargs))
   (apply #'call-next-method method
          :function (lambda (args &rest other-args)
                      (declare (dynamic-extent other-args))
                      (raise-method
                        (method-generic-function method)
                        args)
                      (apply function args other-args))
          initargs))

Many MOP implementations don't adhere to the MOP spec here, though.


Pascal

-- 
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/