From: Peter Seibel
Subject: :around methods and call-next-method
Date: 
Message-ID: <m3smee2rc1.fsf@javamonkey.com>
Okay, I've been thinking about this a bit and it seems like it would
be pretty crazy[*] to write :around methods that *don't* call
CALL-NEXT-METHOD somewhere. My thinking is that an :around method
anywhere in the hierarchy that does't call CALL-NEXT-METHOD takes on
itself whole responsibility for implementing the generic function for
whatever parameter specializers it uses *and all their subclasses*.
I.e. once such an :around method exists there's no point in writing
more specific methods on that GF except for possibly another :around
method. At which point you've reduced the whole method combination
machinery to a Java/C++/Python style, do-this-and-maybe-call-upwards
style of method combination. So as a matter of good style and good
sense it seems that the point of every :around method should be to
wrap something *around* a call to CALL-NEXT-METHOD which will thus
eventually (after perhaps passing through other such :around methods)
end up at the "real" implementation in terms of :before, :after, and
primary methods. Does that sound right?

-Peter

[*] As in, do not try this at home kids, almost certainly a bad idea
that you're going to live to regret, crazy.

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp

From: Kenny Tilton
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <Gbimc.93052$WA4.66767@twister.nyc.rr.com>
Damn, I thought the next thing we were going to hear from you was "It 
shipped!". :)

Peter Seibel wrote:
> Okay, I've been thinking about this a bit and it seems like it would
> be pretty crazy[*] to write :around methods that *don't* call
> CALL-NEXT-METHOD somewhere.

By "somewhere" do you mean "in at least one possible code path"? ie, Are 
you less horrified by (unless XXXX (call-next-method))?

kenny

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Wade Humeniuk
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <Tiimc.25472$U75.8380@edtnps89>
Peter Seibel wrote:

> Okay, I've been thinking about this a bit and it seems like it would
> be pretty crazy[*] to write :around methods that *don't* call
> CALL-NEXT-METHOD somewhere. My thinking is that an :around method
> anywhere in the hierarchy that does't call CALL-NEXT-METHOD takes on
> itself whole responsibility for implementing the generic function for
> whatever parameter specializers it uses *and all their subclasses*.
> I.e. once such an :around method exists there's no point in writing
> more specific methods on that GF except for possibly another :around
> method. At which point you've reduced the whole method combination
> machinery to a Java/C++/Python style, do-this-and-maybe-call-upwards
> style of method combination. So as a matter of good style and good
> sense it seems that the point of every :around method should be to
> wrap something *around* a call to CALL-NEXT-METHOD which will thus
> eventually (after perhaps passing through other such :around methods)
> end up at the "real" implementation in terms of :before, :after, and
> primary methods. Does that sound right?

I would give that a definite maybe.  Here is some examples of real life
:around methods I have written.  They all call-next-method and
some call themselves on their children.  There are probably some cases
where an :around method would not call-next-method based on some
condition not being met.

(defmethod handle-event :around ((handler simple-event-handler) event)
   (loop for *current-handler* in (children handler)
         do
         (handler-case (progn (handle-event *current-handler* event) (return-from handle-event))
           (no-primary-handler () (values))
           (call-next-handler () (values))))
   (call-next-method))

(defmethod start-handler :around ((handler simple-event-handler))
   (if (parent handler)
       (push handler (children (parent handler)))
     (push handler *top-level-handlers*))
   (call-next-method))

(defmethod exit-handler :around ((handler simple-event-handler) &optional result)

   (loop for *current-handler* = (pop (children handler))
         while *current-handler*
         do (handler-case (exit-handler *current-handler* result)
              (no-primary-handler () (values))
              (call-next-handler () (values))))

   (let ((parent (parent handler)))
     (unwind-protect
         (call-next-method)
       (if parent
           (progn
             (setf (children parent) (delete handler (children parent)))
             (when (exit-function handler)
               (send-internal-event (make-instance 'child-exited
                                                   :child handler
                                                   :parent parent
                                                   :result result
                                                   :exit-function (exit-function handler)))))
From: Wade Humeniuk
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <umimc.25566$U75.23080@edtnps89>
Wade Humeniuk wrote:

> I would give that a definite maybe.  Here is some examples of real life
> :around methods I have written.  They all call-next-method and
> some call themselves on their children.  There are probably some cases
> where an :around method would not call-next-method based on some
> condition not being met.
> 

Oooh!  handle-event (below) will only call-next-method if a child
does not handle it.

> (defmethod handle-event :around ((handler simple-event-handler) event)
>   (loop for *current-handler* in (children handler)
>         do
>         (handler-case (progn (handle-event *current-handler* event) 
> (return-from handle-event))
>           (no-primary-handler () (values))
>           (call-next-handler () (values))))
>   (call-next-method))

Wade
From: Pascal Costanza
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <c7csen$p73$1@newsreader2.netcologne.de>
Peter Seibel wrote:

> Okay, I've been thinking about this a bit and it seems like it would
> be pretty crazy[*] to write :around methods that *don't* call
> CALL-NEXT-METHOD somewhere.

A typical example is this:

(defmethod make-an-expensive-calculation :around
   (... params ...)
   (let ((result (get-cached-result ... params ...)))
     (if result result
        (setf (get-cached-result ... params ...)
              (call-next-method)))))

I think there are also other examples.


Pascal

-- 
1st European Lisp and Scheme Workshop
June 13 - Oslo, Norway - co-located with ECOOP 2004
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
From: Peter Seibel
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <m3fzad393n.fsf@javamonkey.com>
Pascal Costanza <········@web.de> writes:

> Peter Seibel wrote:
>
>> Okay, I've been thinking about this a bit and it seems like it would
>> be pretty crazy[*] to write :around methods that *don't* call
>> CALL-NEXT-METHOD somewhere.
>
> A typical example is this:
>
> (defmethod make-an-expensive-calculation :around
>    (... params ...)
>    (let ((result (get-cached-result ... params ...)))
>      (if result result
>         (setf (get-cached-result ... params ...)
>               (call-next-method)))))
>
> I think there are also other examples.

So I guess this is in the spirit of my theory since it does call
CALL-NEXT-METHOD once. All the more specific :before/:after/primary
methods will get called at that time. (Though one might want to think
about clearing the cached results if new methods are added to the GF.)

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Espen Vestre
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <kwad0mge40.fsf@merced.netfonds.no>
Peter Seibel <·····@javamonkey.com> writes:

> CALL-NEXT-METHOD somewhere. My thinking is that an :around method
> anywhere in the hierarchy that does't call CALL-NEXT-METHOD takes on
> itself whole responsibility for implementing the generic function for
> whatever parameter specializers it uses *and all their subclasses*.

I'd say it depends on the intended semantics of the GF, and that sometimes,
if that semantics is clearly defined, it can be very useful to let the
:around-method stop any further execution.
-- 
  (espen)
From: Peter Seibel
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <m3brl1390x.fsf@javamonkey.com>
Espen Vestre <·····@*do-not-spam-me*.vestre.net> writes:

> Peter Seibel <·····@javamonkey.com> writes:
>
>> CALL-NEXT-METHOD somewhere. My thinking is that an :around method
>> anywhere in the hierarchy that does't call CALL-NEXT-METHOD takes on
>> itself whole responsibility for implementing the generic function for
>> whatever parameter specializers it uses *and all their subclasses*.
>
> I'd say it depends on the intended semantics of the GF, and that sometimes,
> if that semantics is clearly defined, it can be very useful to let the
> :around-method stop any further execution.

Do you have an actual example? I'm certainly not going to say there's
*never* a use for it but given the weird interaction with more
specific :before/:after/primary methods it seems like it'd be a bit of
an odd beast.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Espen Vestre
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <kwu0yteggr.fsf@merced.netfonds.no>
Peter Seibel <·····@javamonkey.com> writes:

> Do you have an actual example? 

yes. I'm not sure if it's good though, but I found a couple of examples
in my code and one of them is:

(defmethod re-initialize-updated-rows :around ((pt price-table))
  (when (cells-of pt)
    (call-next-method)))

This :around-method is just a shortcut that ensures that this gf is just
skipping whatever it tries to do if the table has no cells. There are
lots of subclasses to price-table but no other :around-methods, and there
are two (and possibly more to come) primary methods. Each of these primary
methods would have to test if there are any cells, if this hadn't already
been done in the around-method.

Sometimes I provide :after-methods to accessor/setter functions that have
(costly) side-effects. On one occasion I've used an around-method on
the (setf <accessor>)-method to short cut in the case where the value 
hasn't really changed:

(defmethod (setf num-shown-of) :around (value (tab mbp))
  (if (ignore-errors (eql (num-shown-of tab) value))
      ;; no change - avoid other methods!
      value
    (call-next-method)))

-- 
  (espen)
From: Robert St. Amant
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <lpn3c6ds8wv.fsf@haeckel.csc.ncsu.edu>
Peter Seibel <·····@javamonkey.com> writes:

> Okay, I've been thinking about this a bit and it seems like it would
> be pretty crazy[*] to write :around methods that *don't* call
> CALL-NEXT-METHOD somewhere.

I'm sure this isn't exactly what you're thinking of, but sometimes
when building on someone else's system, I've found it useful to write
an :around method to temporarily and completely replace an existing
method's functionality.  I can fiddle with my code until I get it
right, I can easily restore the original functionality without
reloading files, and (if I have the source for the existing system) my
editor can still find the original method with meta-dot.  Of course,
in the end, I get rid of the :around method.

-- 
Rob St. Amant
http://www4.ncsu.edu/~stamant
From: Peter Seibel
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <m3y8o51e46.fsf@javamonkey.com>
·······@haeckel.csc.ncsu.edu (Robert St. Amant) writes:

> Peter Seibel <·····@javamonkey.com> writes:
>
>> Okay, I've been thinking about this a bit and it seems like it would
>> be pretty crazy[*] to write :around methods that *don't* call
>> CALL-NEXT-METHOD somewhere.
>
> I'm sure this isn't exactly what you're thinking of, but sometimes
> when building on someone else's system, I've found it useful to write
> an :around method to temporarily and completely replace an existing
> method's functionality.  I can fiddle with my code until I get it
> right, I can easily restore the original functionality without
> reloading files, and (if I have the source for the existing system) my
> editor can still find the original method with meta-dot.  Of course,
> in the end, I get rid of the :around method.

Sure--when used as a temporary measure anything is legit; get the job
done, figure out what you need to figure out, and move on. I'm with
that.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Kenny Tilton
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <Koymc.95982$WA4.38298@twister.nyc.rr.com>
>>Peter Seibel <·····@javamonkey.com> writes:
>>
>>
>>>Okay, I've been thinking about this a bit and it seems like it would
>>>be pretty crazy[*] to write :around methods that *don't* call
>>>CALL-NEXT-METHOD somewhere.

Here's one. This is old code and I forget why I had to handle a null 
to-res, but fwiw I do not write stuff like this without a reason. This 
looks like it is doing the usual Lisp thing of handling nil gracefully, 
in this case a conversion from one resolution to a null resolution, in 
which case it returns the "geometric thing" unchanged. geothing can be a 
number, a point, or a rectangle.

(defmethod nres-to-res :around (geo-thing from-Res (to-res null))
   (declare (ignore from-res))
   geo-thing)

Of course I could as easily have not specialized tores and coded:

   (if to-res (call-next-method) geo-thing)

But I do not think that makes the first version "pretty crazy". I think 
nowadays I would code the second version. I do recall doing some 
amateurish benchmarking and determining that I got better performance 
(with allegrocl/win) by specializing GFs to achieve branches based on 
type. Those involved alternatives requiring typep so the lesson might 
not apply to a simple test for nil, but that might have been the 
influence that drove me to code version one.

kenny

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Peter Seibel
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <m3u0ys27ar.fsf@javamonkey.com>
Kenny Tilton <·······@nyc.rr.com> writes:

>>>Peter Seibel <·····@javamonkey.com> writes:
>>>
>>>
>>>>Okay, I've been thinking about this a bit and it seems like it would
>>>>be pretty crazy[*] to write :around methods that *don't* call
>>>>CALL-NEXT-METHOD somewhere.
>
> Here's one. This is old code and I forget why I had to handle a null
> to-res, but fwiw I do not write stuff like this without a reason. This
> looks like it is doing the usual Lisp thing of handling nil
> gracefully, in this case a conversion from one resolution to a null
> resolution, in which case it returns the "geometric thing" unchanged.
> geothing can be a number, a point, or a rectangle.
>
> (defmethod nres-to-res :around (geo-thing from-Res (to-res null))
>    (declare (ignore from-res))
>    geo-thing)
>
> Of course I could as easily have not specialized tores and coded:
>
>    (if to-res (call-next-method) geo-thing)
>
> But I do not think that makes the first version "pretty crazy".

So I've thought about this some more based on folks' responses and
have refined my position. My new position is this: if I write an
:around method that doesn't always call CALL-NEXT-METHOD then I'm
putting a stake in the ground and saying, "In the situations where I
don't call CALL-NEXT-METHOD, I am providing a method that will
correctly implement the GF's semantics for all current *and* future
subclasses of my parameter specializers." To the extent this seems
like a reasonable claim to make, then such an :around method is
non-crazy.

In your example it is part of the semantics of nres-to-res's that if
to-res in null then the answer is geo-thing regardless of what class
geo-thing or from-res are. Thus the :around method you wrote is
non-crazy as long as--as seems likely--there is no possible
specializer of geo-thing or from-res that could require a different
result.

Similarly the memoizing example Pascal gave is easily understood under
my new theory: the only circumstance it doesn't CALL-NEXT-METHOD is
when it already has a value computed by doing so. In this case that
works for all possible specializations because if more specific
methods are added they still get a crack at producing the answer.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Marc Battyani
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <c7fo12$tf1@library1.airnews.net>
"Peter Seibel" <·····@javamonkey.com> wrote

> Similarly the memoizing example Pascal gave is easily understood under
> my new theory: the only circumstance it doesn't CALL-NEXT-METHOD is
> when it already has a value computed by doing so. In this case that
> works for all possible specializations because if more specific
> methods are added they still get a crack at producing the answer.

I also use this kind of memoizing.
Just to give you another example, in my 3d library I have this:

(defmethod draw :around ((object 3d-object))
  (when (bad-display-list object)
        (delete-display-list object))
  (if (use-display-list object)
      (if (display-list object)
          (gl:gl-call-list (display-list object))
          (let ((n (gl:gl-gen-lists 1)))
            (when (plusp n)
              (gl:gl-new-list n gl:*gl-compile-and-execute*)
              (call-next-method)
              (gl:gl-end-list)
              (setf (display-list object) n))))
      (call-next-method)))

In short if the object has a display list I play it otherwise I create one.
That way the 3d-objects only have to provide the drawing methods, all the
display list stuff is handled by the around method.

Marc
From: Raymond Wiker
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <868yg5qmnw.fsf@raw.grenland.fast.no>
Peter Seibel <·····@javamonkey.com> writes:

> ·······@haeckel.csc.ncsu.edu (Robert St. Amant) writes:
>
>> Peter Seibel <·····@javamonkey.com> writes:
>>
>>> Okay, I've been thinking about this a bit and it seems like it would
>>> be pretty crazy[*] to write :around methods that *don't* call
>>> CALL-NEXT-METHOD somewhere.
>>
>> I'm sure this isn't exactly what you're thinking of, but sometimes
>> when building on someone else's system, I've found it useful to write
>> an :around method to temporarily and completely replace an existing
>> method's functionality.  I can fiddle with my code until I get it
>> right, I can easily restore the original functionality without
>> reloading files, and (if I have the source for the existing system) my
>> editor can still find the original method with meta-dot.  Of course,
>> in the end, I get rid of the :around method.
>
> Sure--when used as a temporary measure anything is legit; get the job
> done, figure out what you need to figure out, and move on. I'm with
> that.

        Somebody else hinted at this, but memoization of a method
could also use an :around-method, and only use call-next-method if
there was no memoized value. (Actually, I'm not sure this is correct;
is it legal to capture the return value from call-next-method an
perform some computation on it, eventually returning something that
may or may not be something completely different?)

-- 
Raymond Wiker                        Mail:  ·············@fast.no
Senior Software Engineer             Web:   http://www.fast.no/
Fast Search & Transfer ASA           Phone: +47 23 01 11 60
P.O. Box 1677 Vika                   Fax:   +47 35 54 87 99
NO-0120 Oslo, NORWAY                 Mob:   +47 48 01 11 60

Try FAST Search: http://alltheweb.com/
From: Steven M. Haflich
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <myEmc.61689$yM6.5263@newssvr25.news.prodigy.com>
(defmethod print-object :around ((x (eql pi)) (stream my-stream))
   (write-string "#.pi" stream))

Ignore the horrendous efficiency issues this eql method might impose.
Now, why might I want to write this :around method instead of simply
coding a similar primary method?

Because it prevents any lower-precedence :around methods from running,
and prevents all :before and :after methods from running.

An :around method that _never_ executes call-next-method is a way of
coding an exclusion range of applicability of some methods.  Perhaps a
more believable example would be something like this:

(defmethod print-object :around ((x double-float) (stream my-stream))
    ;; The intended consumer of this stream doesn't understand
    ;; double-floats.
   (print-object (coerce x 'single-float) stream))
From: Peter Seibel
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <m3llk425ty.fsf@javamonkey.com>
"Steven M. Haflich" <·················@alum.mit.edu> writes:

> (defmethod print-object :around ((x (eql pi)) (stream my-stream))
>    (write-string "#.pi" stream))
>
> Ignore the horrendous efficiency issues this eql method might impose.
> Now, why might I want to write this :around method instead of simply
> coding a similar primary method?
>
> Because it prevents any lower-precedence :around methods from running,
> and prevents all :before and :after methods from running.
>
> An :around method that _never_ executes call-next-method is a way of
> coding an exclusion range of applicability of some methods.  Perhaps a
> more believable example would be something like this:
>
> (defmethod print-object :around ((x double-float) (stream my-stream))
>     ;; The intended consumer of this stream doesn't understand
>     ;; double-floats.
>    (print-object (coerce x 'single-float) stream))

So my anxiety about this method is that someone is going to come along
and subclass my-stream and then try to write a :before, :after, or
primary method on print-object that specializes stream on that new
class and then wonder why their method never seems to run. (Yes, if
they're aware of this potential problem and have a MOP in their
implementation they could at least check to see if this is what's
happening. But it is still a bit of weird action at a distance that
gives me the heebie-jeebies.)

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Steven M. Haflich
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <FUEmc.6025$t_2.531@newssvr27.news.prodigy.com>
Peter Seibel wrote:

> So my anxiety about this method is that someone is going to come along
> and subclass my-stream and then try to write a :before, :after, or
> primary method on print-object that specializes stream on that new
> class and then wonder why their method never seems to run. (Yes, if
> they're aware of this potential problem and have a MOP in their
> implementation they could at least check to see if this is what's
> happening. But it is still a bit of weird action at a distance that
> gives me the heebie-jeebies.)

Many years ago during the CLOS standardization process (I think it was
at the time we retracted generic-labels and generic-flet, because no
one understood their intended semantics, but I may misremember) Richard
Gabriel made an insightful observation.  I cannot remember his exact
point, but the essence was that modern languages place great emphasis
on lexical analysis and referential locality imposing good behavior on
functions.  >>>But generic functions have distributed implementation.<<<
I remember he pointed out that the implementation of a function that
happens to be a generic-function can not oly span multiple lexical
extents, it can span multiple files.

My observation is that rpg was correct, that gf's allow distributed
implementation of functions.  They provide some intuitive (and also
introspectable) semantics for how distributed fragments of the gf
interact, but if you're not happy with distributed implementation of
functions, then you won't be and shouldn't be happy with CLOS.

"O-O programming is supremely powerful -- it can squeeze the toothpaste
all the way from one end of the tube to the other." - smh 2004
From: Joe Marshall
Subject: Re: :around methods and call-next-method
Date: 
Message-ID: <d65guuuk.fsf@ccs.neu.edu>
"Steven M. Haflich" <·················@alum.mit.edu> writes:

> My observation is that rpg was correct, that gf's allow distributed
> implementation of functions.  They provide some intuitive (and also
> introspectable) semantics for how distributed fragments of the gf
> interact, but if you're not happy with distributed implementation of
> functions, then you won't be and shouldn't be happy with CLOS.

Page 21 and 22 of Steele's  ``Lambda:  the Ultimate Declarative''
discuss this a bit.  He shows a matrix of operations and operands:


           TYPE        PRINT      ATOM     CAR   ...
Operand   
0          RET(FIXNUM) TYO("0")   RET(T)   ERROR ...

43         RET(FIXNUM) TYO("4")   RET(T)   ERROR ...

(A B)      RET(LIST)   TYO("(")   RET(NIL) RET(A) ...
                       TYO("A")
                       TYO(" ")
                       TYO("B")
                       TYO(")")

[1]        RET(VECTOR) TYO("[")   RET(NIL) RET(1)
                       TYO("1")
                       TYO("]")

Data-driven programming involves finding the appropriate
implementation in that matrix.  One method is to code in a
column-oriented way:

(defun car (x)
  (etypecase x
    (cons (...implementation of cons...))
    (vector (svref x 0))
    ...))

The disadvantage of this method is that when you add a new data type
you have to find all the type dispatches and extend them.

The `object-oriented style' is to code in a row-oriented way:

class Cons {

  TypeCode TypeOf() {
    return TypeCode.FIXNUM;
    }

  void Print (Stream stream) {
    car.print(stream);
    " ".print(stream);
    cdr.print(stream);
    }

  Boolean Atom () {
    return FALSE;
    }
}

The disadvantage of this method is that when you add a new operation
you have to find all the relevant classes and extend them.

CLOS-style generic functions give you a point-by-point way:

(defmethod car ((x <pair>))
  (slot-value x 'car))

(defmethod atom ((x <number>)) 
   't)

An advantage of CLOS here is that you can arrange the methods in
aesthetically pleasing rows or columns as you see fit without having
to buy into any particular primary dispatch paradigm.  This also
allows you to scatter your methods all over the place with no
aesthetics whatsoever.