And now for something completely different.... 
Author Message
 And now for something completely different....

... suppose you have a class (hierarchy) and a bunch of methods.

Now you want to add some before, after and around methods to your
application. With a constraint: no new classes can be introduced, and,
above all, user :before, :after and :around methods should work
without modifications. I.e. suppose I have

(defclass my-class (super1 super2 #|...|# superN) #|...|#)

(defmethod zut ((c my-class)))

(defmethod zut :before ((c my-class)))

(define-extra-method zut :before ((c my-class)) #|....|#)

I.e. How do I define the 'extra method' without clobbering the
(zut :before (my-class)) one?

NB. The solution "define a method combination" does not seem to work
because it'd require you to fix the method combination slot of the
generic function.
The "let's rewrite an :around method any time we do a
define-extra-method" seems better, but I foresee problems regarding
the definition environment and recompilation.

Anybody has some insight on this problem?

Kudos and cotillons to the person who figures out what I have been
looking at in the past two days. :)

Cheers

--
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
http://www.*-*-*.com/ ~marcoxa



Mon, 15 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> Anybody has some insight on this problem?

Why do you have a constraint that you can't add classes?
This cripples you.  If you're in a position to add methods,
I'd think you'd be in a position to add classes... mostly.

And why do you have a problem adding additional methods?
NORMALLY, only the person who maintains the code for a class
should be adding methods to it.  It's "impolite" to add methods
to a class you don't maintain the defclass for, IMO.
As such, if you own all the code, seems you're just saying
"I want half the code in one form and half in another" and
this problem reduces to the question:

I want to be able to do
(defun foo () ...)
and later
(defun-also foo () ...)
and I can't figure out how to jam two definitions into one function
cell.  That is, the problem isn't about CLOS at all, it's about
distributed function definition.

My take on this is that you have overconstrained the problem.
That doesn't mean there isn't an out, but it means you have to very
carefully examine each of your constraints and you have to be
pretty creative about what you can do in addition to what you
might have said.  For example, in the defun case, a possible solution
is
 (defun-some foo () ...)
 (defun-some foo () ...)
followed later by
 (ok-i\'m-done-defunning foo)
And such a solution would work in the clos case, too.

The problem you're having is that you're trying to still exploit
the "distributed definition" part of CLOS which relies on a shared
model of "how to merge a new definition with a previous one" yet you
haven't anywhere written code to do that merge.  Moreover, you are
neglecting that (method foo :after) amounts to a "name" in the sense
that the CLOS method combination code does bookkeeping under this name
and sees a further definition as a replacement of that name.  If you
want two "names" (two before methods), you need to make syntactic space
for the CLOS code to hold the two names, or else you shouldn't be
surprised that you can't wedge two definitions into the same mailbox.
Either you have to modify the system itself or you have to use external
storage, in which case you have to create a "time" when the seam-up
is done (such as the ok-xxx operator I had above) or you have to maintain
an auxiliary data structure so that every time a "definition" is done,
the "real definition" is recreated from scratch from the contributed parts
stored separately (which is what CLOS does).

This probably isn't the answer you wanted.  But maybe it helps you
understand the question you're asking.



Mon, 15 Oct 2001 03:00:00 GMT  
 And now for something completely different....


Quote:

>> Anybody has some insight on this problem?

>Why do you have a constraint that you can't add classes?
>This cripples you.  If you're in a position to add methods,
>I'd think you'd be in a position to add classes... mostly.

>And why do you have a problem adding additional methods?
>NORMALLY, only the person who maintains the code for a class
>should be adding methods to it.  It's "impolite" to add methods
>to a class you don't maintain the defclass for, IMO.
>As such, if you own all the code, seems you're just saying
>"I want half the code in one form and half in another" and
>this problem reduces to the question:

Actually, CLOS allows some relaxation of that impoliteness.  If the generic
function is part of your own application, I think you can feel free to
define methods for someone else's classes.  This is one of the great things
about the fact that CLOS doesn't require pre-declaration of all the methods
in the class specification (compare with C++'s class declarations).  But if
you're not the "owner" of the class or the GF, you shouldn't be adding
methods.

That said, I have had to do the kinds of things Marco asks about in the
past.  When I was doing Lisp programming at Thinking Machines, we
occasionally needed to customize the behavior of internal mechanisms on the
Lisp system.  We generally used the ADVISE mechanism; this isn't standard,
but it was available in all the Lisp implementations we cared about
(Symbolics and Lucid).  This is essentially the "defun-also" mechanism that
the original poster asked about.

--

GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.



Mon, 15 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> That said, I have had to do the kinds of things Marco asks about in the
> past.  When I was doing Lisp programming at Thinking Machines, we
> occasionally needed to customize the behavior of internal mechanisms on the
> Lisp system.  We generally used the ADVISE mechanism; this isn't standard,
> but it was available in all the Lisp implementations we cared about
> (Symbolics and Lucid).  This is essentially the "defun-also" mechanism that
> the original poster asked about.

The only uses I know for advise are here there wasn't beforehand protocol
cooperation, which doesn't sound like it's the lay of the land here.

I usually use other devices for things that some people would do this
defun-also thing. e.g.,

 (in-package "A")
 (defvar *foo-hooks* '())
 (defun foo (x)
   (mapcar #'funcall *foo-hooks* (circular-list x))
   ...)
 - - - - - later module
 (in-package "B")
 (defun foo-also (x) ...)
 (push 'foo-also a:*foo-hooks*)

I like this kind of approach because both parties have to acknowledge
their involvement in the cooperation and because it doesn't involve
any prior knowledge by A of B, but B also doesn't affect A's code--only
A's data.  I know people that prefer to use ADVISE for this, but I
really don't like that.  Of course, one can macrofy this.



Mon, 15 Oct 2001 03:00:00 GMT  
 And now for something completely different....
I agree with much of Kent's reply, that essentially you're asking
to put multiple things in a box that only holds one thing, i.e.
it's the same as asking to have multiple code for a function,
where a function has only one code.  Of course, CLOS methods
are exactly this capability relative to function, you CAN add
more code to one generic function.  That said, the typical
way to have "system" code not interact with "user" code for
a method is to define an internal method that is called which
the system code (or user code) extends.  

I do this for the NiCORE persistent object system, by having
a initialize-persistent-instance method that the system extends,
leaving the user to extend initialize.  In your case, you must
do it the other way because the system wasn't designed for
user extension:

(defmethod zut :around ((c my-class))
  (zut-extra-before c)
  (call-next-method)
  (zut-extra-after c)
  )

;; excuse the silkscript syntax..

(macro define-extra-method (name when args [body]*)
 (if (eq when :before)
   then
   `(method {(new-symbol name '-extra-before)} :before {args} {body}*)
   elseif (eq when :after)
    then
   `(method {(new-symbol name '-extra-after)}  :after {args} {body}*)  
   else
    ;; pick before or after
   `(method {(new-symbol name '-extra-after)} {args} {body}*)      
    ))


  NiCLOS engineering version released!  http://www.niclos.com/

Quote:

> ... suppose you have a class (hierarchy) and a bunch of methods.

> Now you want to add some before, after and around methods to your
> application. With a constraint: no new classes can be introduced, and,
> above all, user :before, :after and :around methods should work
> without modifications. I.e. suppose I have

> (defclass my-class (super1 super2 #|...|# superN) #|...|#)

> (defmethod zut ((c my-class)))

> (defmethod zut :before ((c my-class)))

> (define-extra-method zut :before ((c my-class)) #|....|#)

> I.e. How do I define the 'extra method' without clobbering the
> (zut :before (my-class)) one?

> NB. The solution "define a method combination" does not seem to work
> because it'd require you to fix the method combination slot of the
> generic function.
> The "let's rewrite an :around method any time we do a
> define-extra-method" seems better, but I foresee problems regarding
> the definition environment and recompilation.

> Anybody has some insight on this problem?

> Kudos and cotillons to the person who figures out what I have been
> looking at in the past two days. :)

> Cheers

> --
> Marco Antoniotti ===========================================
> PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
> tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
> http://www.parades.rm.cnr.it/~marcoxa



Mon, 15 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> Kudos and cotillons to the person who figures out what I have been
> looking at in the past two days. :)

an implementation of a parallel computation model (eg., a hardware
description language)?

thi



Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> (defclass my-class (super1 super2 #|...|# superN) #|...|#)

> (defmethod zut ((c my-class)))

> (defmethod zut :before ((c my-class)))

> (define-extra-method zut :before ((c my-class)) #|....|#)

> I.e. How do I define the 'extra method' without clobbering the
> (zut :before (my-class)) one?

A (implementation specific/MOP dependent) way of doing this might be to get
the function object corresponding to the original :before method. I imagine
it would then be relatively simple to enclose that object around a fresh
definition of a :before method, letting you hold on to all prior methods.

Sunil



Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....
I can think of two broad categories where this comes up:

I. Design for open implementation.  If you're designing an application
that you want users to be able to extend and customize, then THEY will
be under the constraints you give, but YOU will not.  YOU can define
additional classes and/or methods.  The common techniques are:
  A. Sprinkle dummy mixin classes into public ones.  This gives you a
place to
     hang new methods in patches, etc.  A typical arrangement is:
      (defclass FOO (custom-foo foo-implementation) ())
     where the implemenation hangs its methods on foo-implementation.
  B. Layer the major public methods with subprotocols.  As in the AMOP,
you
     will need to tell the user which methods can be overwritten or
extended,
     where extension is defined as something that still calls the
     implementation's method.
  C. Have your implementation call internal methods, which are layered,
and
     only document the things that it calls.  This gives you room to to
     specialize your code, while leaving the users to specialize the
public      
     stuff.

None of these help you where there is more than one independent body
which wants to be able to separately make customizations/patches which
can be simultaneously loaded.  For this I think you need some
formalization of the following...

II. Hacking an existing implementation.  Here the restrictions may
really be on you.  In addition to ADVISE, if available, remember that if
the CLOS MOP is available to you, you can write code which, at compile
time, locates currently existing applicable methods, grabs their
method-functions, and calls them explicitly within the body of the new
method.  You can glomm together as much as you want this way.  Be
careful, though, if you want to be able to reload such code (i.e. load
it more than once), then you'd better find a way to keep track of which
existing methods you're replacing, and making sure you don't call
yourself as many times as you have reloaded the code.  DEFVAR is your
friend.

Quote:

> ... suppose you have a class (hierarchy) and a bunch of methods.

> Now you want to add some before, after and around methods to your
> application. With a constraint: no new classes can be introduced, and,
> above all, user :before, :after and :around methods should work
> without modifications. I.e. suppose I have

> (defclass my-class (super1 super2 #|...|# superN) #|...|#)

> (defmethod zut ((c my-class)))

> (defmethod zut :before ((c my-class)))

> (define-extra-method zut :before ((c my-class)) #|....|#)

> I.e. How do I define the 'extra method' without clobbering the
> (zut :before (my-class)) one?

> NB. The solution "define a method combination" does not seem to work
> because it'd require you to fix the method combination slot of the
> generic function.
> The "let's rewrite an :around method any time we do a
> define-extra-method" seems better, but I foresee problems regarding
> the definition environment and recompilation.

> Anybody has some insight on this problem?

> Kudos and cotillons to the person who figures out what I have been
> looking at in the past two days. :)

> Cheers

> --
> Marco Antoniotti ===========================================
> PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
> tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
> http://www.parades.rm.cnr.it/~marcoxa



Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:


> > (defclass my-class (super1 super2 #|...|# superN) #|...|#)

> > (defmethod zut ((c my-class)))

> > (defmethod zut :before ((c my-class)))

> > (define-extra-method zut :before ((c my-class)) #|....|#)

> > I.e. How do I define the 'extra method' without clobbering the
> > (zut :before (my-class)) one?

> A (implementation specific/MOP dependent) way of doing this might be to get
> the function object corresponding to the original :before method. I imagine
> it would then be relatively simple to enclose that object around a fresh
> definition of a :before method, letting you hold on to all prior methods.

The problem here is that you need a way of accomodating redefinition.
It may or may not be apparent, but the entire language is organized
around the idea that you might need to re-evaluate any definition
in isolation in an interactive editor.  That's impossible to assure
in general because there is an order-of-evalution issue that can't
be escaped when top-level executable forms are present, but my point
is that if what you're encouraging is the idea that it just do
 (setf (symbol-function symbol)
       (functionally-encapsulate new (symbol-function symbol)))
or the equivalent, the problem is that you're making it very dependent
on load order in a way that CLOS is not.  That doesn't mean it can't
work that way, but it's more fragile.  It also means that re-evaluating
a lone definition risks having the function called twice.

Then again, my recollection is that the lisp machine uses a scheme
similar to what it sounds to me like you're proposing.  In that model,
there is an object that can live in a function cell which is an
"encapsulation" so that in addition to wrapping new definitions around
old ones, you can, for example, also notice that an existing
definition is encapsulated and decide to remove the 2nd innermost
encapsulation leaving the others in place.  This is used for tracing,
advising, and other things that share a common value cell.  But it
pretty much relies on the introspective leverage offered by saying
that the encapsulation is a first-class inspectable/manipulable object
with data components you can read and modify rather than just a black
box that you can funcall like CL functions basically are.

Disclaimer: I'm being slightly vague in the first paragraph above
because I'm not 100% sure I understood your suggestion and that I'm
responding to what you actually said.  I'm being slightly vague in the
second paragraph because my memory of the lispm functionality is
potentially faulty and I'm too lazy to move to the chair next to me to
poke around on my macivory and find out for sure.



Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:
> > A (implementation specific/MOP dependent) way of doing this might be to get
> > the function object corresponding to the original :before method. I imagine
> > it would then be relatively simple to enclose that object around a fresh
> > definition of a :before method, letting you hold on to all prior methods.

> The problem here is that you need a way of accomodating redefinition.
> It may or may not be apparent, but the entire language is organized
> around the idea that you might need to re-evaluate any definition
> in isolation in an interactive editor.  That's impossible to assure
> in general because there is an order-of-evalution issue that can't
> be escaped when top-level executable forms are present, but my point
> is that if what you're encouraging is the idea that it just do
>  (setf (symbol-function symbol)
>        (functionally-encapsulate new (symbol-function symbol)))
> or the equivalent, the problem is that you're making it very dependent
> on load order in a way that CLOS is not.  That doesn't mean it can't
> work that way, but it's more fragile.  It also means that re-evaluating
> a lone definition risks having the function called twice.

Good point, this would most certainly be dependent on load order. But then,
going back to what Marco had originally asked:

Quote:


> > > (defclass my-class (super1 super2 #|...|# superN) #|...|#)

> > > (defmethod zut ((c my-class)))

> > > (defmethod zut :before ((c my-class)))

> > > (define-extra-method zut :before ((c my-class)) #|....|#)

> > > I.e. How do I define the 'extra method' without clobbering the
> > > (zut :before (my-class)) one?

It would appear that ensuring a general, fool-proof approach of doing this
sort of thing with :before methods is going to be very difficult. What I
had proposed was a simple hack to get it working.

I'll try to address your objections, as I understood them:

1. If another :before DEFMETHOD with the same specializers is evaluated,
the existing method along with all the extra-methods should vanish. We
would be replacing the old definition (along with all the nexted closures)
with the new definition. At least I find this behavior intuitive. The
alternative is to have the extra-methods hang around irrespective of the
current definition of the :before method. I'm sure this could be done by
hacking into the MOP, but there might be an easier way (below).

2. The behavior of define-extra-method, as Marco has described it, is more
like an advice form, and as such *should* be order dependent. There is no
specification here as to the order in which the extra-methods should
execute. Indeed, there is not even a mechanism to identify them uniquely,
which would make it very difficult to redefine one particular extra method
if it is misbehaving.

I wholly agree that these restrictions would get in the way of flexibly
interacting with and debugging the functions. The best way to get around
this would be to attempt a method combination.

I have never had the need to define a method combination, so what I am
proposing might be severely broken. I'm going by my understanding, rather
than any implementation experience.

(defgeneric zut (c)
  (:method-combination chain))

(defmethod zut (chain :before 'for-some-reason) ((c my-class))
  ...)

My interpretation of method combinations is that CHAIN can be written to
generate a combination that collects multiple :before methods and executes
them all. The method definition rules at the same time should ensure that
the :before methods do not get redefined unless the reason matches. The
primary method will be one without a reason attached. This should address
all the problems the original proposal had. (If I had the time, I'd have
written some sample code and tested it out. Unfortunately...)

Sunil



Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> I wholly agree that these restrictions would get in the way of flexibly
> interacting with and debugging the functions. The best way to get around
> this would be to attempt a method combination.

or adopt a naming strategy in which the name *does* imply ordering,
which is what the class precedence list does.  that's why, incidentally,
the cpl is totally ordered--to avoid variances in effect due to porting.
it's not conceptually necessary, but it's a real mess in practice if
it's not fully ordered.  and that's also why when this question was first
raised my question was "why disallow class definition" because simply
subclassing (or "superclassing"--that is, interposing a hidden class above
the published class) and putting the new method on the other class would
allow someone who controlled the sources to the class to also write the
code in a way that was not perturbed by load order.  class inheritance
order is a de facto engineering solution to this, and you're working with
your hands tied behind your back when you disallow it.

Quote:
> (defmethod zut (chain :before 'for-some-reason) ((c my-class))
>   ...)

I never used this syntax, but I'm pretty sure a series of non-lists
is the qualifiers.  That is,
 (defmethod zut chain :before for-some-reason ((c my-class)) ...)
Even then, unless you order them alphabetically or have a priority
scheme you still have an ordering problem that subclassing avoids (because
"subclassp" is an ordering predicate, not because of magic).


Tue, 16 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:
> The hard part with any priority scheme is that usually it ends up based
> on numbers.  And numbers are stupid.  They let you deterministically
> order, but they mean nothing and so don't give you any conceptual leverage
> over the problem space.  Ideally you'd like to be able to do something like:
>  (defmethod zut chain :before :call-me fred :must-follow bill ((c my-class))
>     ...)
> but the problem there is that the :must-follow is meta-information not related
> to the name, and if you have to change the ordering to say
>  :must-follow mary
> clos would think you had added a new method, not changed an existing one,
> because it does not distinguish qualifiers for naming purpose from qualifiers
> as omittable attributes.

Agreed. Having been in AI type research this long has given me ample reason
to doubt the power of numbers :-) The priority scheme would have to have
the ordering defined independently of the methods. I don't think it would
work any other way. Numbers are good in the sense that the priorities are
independent of the situation. We could construct a non-numerical approach
for which this is true... I'm thinking along the lines of:

(defmethod zut chain :before fred ((c my-class))
  ...)

(deforder fred bill)

or some such. This should allow for changes in the priority mechanism
independent of the method definitions.

Quote:
> The mechanism CLOS provides, for better or worse,
> that does allow you to manage names in partial orders and composition of
> partial code fragments based on those partial orders is the class structure
> itself.  I really recommend either coming to grips with that or rethinking
> the need to have these multiple definitions or implementing these multiple
> definitions with new meta-linguistic stuff rather than trying to shoehorn
> it into parts of the language not intended for that purpose.

I don't really need any of this, it's been more of a mental game for me :-)
I would hate to have to deal with the complexity of implementing this
elaborate a scheme. I was simply looking for ways of doing it at the method
or generic function level rather than playing with the class structure.

Issues of efficiency, flexibility etc. would have to be resolved at the
application level, so I guess it would be up to Marco to sort out the mess.

Sunil



Wed, 17 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> > I wholly agree that these restrictions would get in the way of flexibly
> > interacting with and debugging the functions. The best way to get around
> > this would be to attempt a method combination.

> or adopt a naming strategy in which the name *does* imply ordering,
> which is what the class precedence list does.  that's why, incidentally,
> the cpl is totally ordered--to avoid variances in effect due to porting.
> it's not conceptually necessary, but it's a real mess in practice if
> it's not fully ordered.  and that's also why when this question was first

Yes, I realize that, and was quite impressed with the attention to detail
when I read about it in AMOP.

Quote:
> raised my question was "why disallow class definition" because simply
> subclassing (or "superclassing"--that is, interposing a hidden class above
> the published class) and putting the new method on the other class would
> allow someone who controlled the sources to the class to also write the
> code in a way that was not perturbed by load order.  class inheritance
> order is a de facto engineering solution to this, and you're working with
> your hands tied behind your back when you disallow it.

There are some issues with subclassing that a method combination would
avoid, as I see it.

1. The proliferation of classes might not be desirable.

2. Subclasses are being used not to distinguish between types of data that
   are genuinely different. It is rather being used for obtain an effect in
   the methods you can define. A method combination would make this more
   obvious.

3. I'm not clear on what class you would make an object belong to. If you
   made an object a subclass of my-class, then creating subclasses of
   my-class for the purpose of method ordering would fail. If you defined
   my-class as a subclass of a set of classes on which you define the other
   methods, I imagine efficiency might become an issue. Further, you would
   need to provide the user with the ability to add a class at run-time if
   this type of ordering is to be part of the exported interface.

4. You would have to go ahead and define all those superclasses for any
   class on which you want this kind of behavior. With method combinations
   you would be able to restrict this behavior to specific generic
   functions rather than enforcing it across all generic functions.

Quote:
> > (defmethod zut (chain :before 'for-some-reason) ((c my-class))
> >   ...)

> I never used this syntax, but I'm pretty sure a series of non-lists
> is the qualifiers.  That is,
>  (defmethod zut chain :before for-some-reason ((c my-class)) ...)

Hmmm, that would make more sense. I was trying to figure out with the
syntax I had presented how defmethod might differentiate the combination
from the argument list.

Quote:
> Even then, unless you order them alphabetically or have a priority
> scheme you still have an ordering problem that subclassing avoids (because
> "subclassp" is an ordering predicate, not because of magic).

Yes, but a priority scheme would not be that hard to add in. It is
potentially expensive though, if for each invocation you have to sort out
the priorities. And it would most definitely be more work than subclassing.

Sunil



Thu, 18 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Quote:

> 2. Subclasses are being used not to distinguish between types of data that
>    are genuinely different. It is rather being used for obtain an effect in
>    the methods you can define. A method combination would make this more
>    obvious.

> 3. I'm not clear on what class you would make an object belong to. If you
>    made an object a subclass of my-class, then creating subclasses of
>    my-class for the purpose of method ordering would fail. If you defined
>    my-class as a subclass of a set of classes on which you define the other
>    methods, I imagine efficiency might become an issue. Further, you would
>    need to provide the user with the ability to add a class at run-time if
>    this type of ordering is to be part of the exported interface.

I'd want to see an actual worked example with non-contrived names before
believing this wasn't going to work, both for efficiency reasons and for
conceptual reasons.  As a rule, I don't think CLOS causes a speed penalty
when the method is inherited from a superclass.  I think  the speed is
identical in the case where you say you imagine efficiency would be an
issue, but absent a real example, i can't be sure.

Quote:
> 4. You would have to go ahead and define all those superclasses for any
>    class on which you want this kind of behavior. With method combinations
>    you would be able to restrict this behavior to specific generic
>    functions rather than enforcing it across all generic functions.

If method combination works, I'm not saying not to use it.
But if it's not working, I'm saying the reason it's probably not is
that you're expecting something to happen by magic.

Quote:
> > > (defmethod zut (chain :before 'for-some-reason) ((c my-class))
> > >   ...)

> > I never used this syntax, but I'm pretty sure a series of non-lists
> > is the qualifiers.  That is,
> >  (defmethod zut chain :before for-some-reason ((c my-class)) ...)

> Hmmm, that would make more sense. I was trying to figure out with the
> syntax I had presented how defmethod might differentiate the combination
> from the argument list.

> > Even then, unless you order them alphabetically or have a priority
> > scheme you still have an ordering problem that subclassing avoids (because
> > "subclassp" is an ordering predicate, not because of magic).

> Yes, but a priority scheme would not be that hard to add in. It is
> potentially expensive though, if for each invocation you have to sort out
> the priorities. And it would most definitely be more work than subclassing.

The hard part with any priority scheme is that usually it ends up based
on numbers.  And numbers are stupid.  They let you deterministically
order, but they mean nothing and so don't give you any conceptual leverage
over the problem space.  Ideally you'd like to be able to do something like:
 (defmethod zut chain :before :call-me fred :must-follow bill ((c my-class))
    ...)
but the problem there is that the :must-follow is meta-information not related
to the name, and if you have to change the ordering to say
 :must-follow mary
clos would think you had added a new method, not changed an existing one,
because it does not distinguish qualifiers for naming purpose from qualifiers
as omittable attributes.  The mechanism CLOS provides, for better or worse,
that does allow you to manage names in partial orders and composition of
partial code fragments based on those partial orders is the class structure
itself.  I really recommend either coming to grips with that or rethinking
the need to have these multiple definitions or implementing these multiple
definitions with new meta-linguistic stuff rather than trying to shoehorn
it into parts of the language not intended for that purpose.

JMO.



Thu, 18 Oct 2001 03:00:00 GMT  
 And now for something completely different....

Ok, I threw the stone and hid my hand :)

Many thanks to all who commented on the subject.

Somehow I had a hunch that method combination could do the (or at
least some) of the trick.  However, I am not so sure that it would
completely clear the issues. As it was pointed out, there are two main
sources of problems.

1 - Everything should be "redefinable".
2 - You do not have access to the actual method <body code>.

I kind of believe that, if not the full MOP, at least some
introspection into the <body code> of a method would be very useful.
Something like

        METHOD-BODY method => code object

Where code object is an immutable opaque structure which could be
combined - at least - in a PROGN like form, with something like

        MAKE-PROGN-METHOD-BODY &rest code objects => code object

Anyway, this may be asking too much; just like the encapsulation
machinery described by KMP.

All in all, I'd settle for the following scheme (I am veering off a
tangent here...) which, I believe, solves some of the problems of
Sunil's :must-follow, DEFORDER proposal.

Right now we can call a single :before or :after method.  I'd like a
method combination that could call a set (i.e. an ordered list) of
(:before :after) methods with the following specification (allow me
not to call it a 'method-combination')

        (defmethod zut :before :first (<method lambda list>) ...)
        (defmethod zut :before :last (<method lambda list>) ...)
        (defmethod zut :before <class> (<method lambda list>) ...)

(:before could be substituted by :after)

In the first case the :before (:after) method is guarateed to be added
first in the set of :before (:after) methods applicable in a given
method call.  The second case is the symmetric one: the method is
guaranteed to be added last.  Of course this introduces a "load eval
compile" order problem, but I presume it is something that can be
lived with.  The key here is that any "current" :before (:after)
method does not get clobbered.

The third case is the more interesting.

First of all let me note that I can rewrite all "standard" :before and
:after method definitions as

        (defmethod zut :before t (<method lambda list>) ...)

why will become clear in a sec.

This form tells me to order the set of :before and :after methods
applicable to a given actual <method lambda list> according to the CPL
of <class>. In particular, when two methods insist on the same <class>
(i.e. the case with 't') the second method defined clobbers the
first; as it happens now. I.e.

        (defmethod zut :before t ((s symbol)) 1)

        (defmethod zut :before t ((s symbol)) 2)

Will result in the call (zut 'foo) to return 2.

Let's see an example

(defclass zot ....)

(defmethod something-about-zot ((z zot)) ...)

;;; Let's add some debugging

(defmethod something-about-zot :before :first ((z zot))
   (format t ">>> ZOT debugging: calling SOMETHING-ABOUT-ZOT with ~S.~% z))

;;; and let's add some locking machinery to the class ZOT, on
;;; operation SOMETHING-ABOUT-LOCK.

(defvar zot-lock (make-lock))

(defmethod something-about-zot :before :first ((z zot))
   (seize-lock zot-lock))

(defmethod something-about-zot :after :first ((z zot))
   (release-lock zot-lock))

In this case I do not really care about the order of execution of the
debugging code and of the locking code. I just care that they get both
executed on operation SOMETHING-ABOUT-ZOT when called on a ZOT
instance.

Soppose I decide that debugging and locking must be executed in order.

I do

(defclass zot-debugging () ())
(defclass zot-debugging-and-locking (zot-debugging) ())

;;; Let's add some debugging

(defmethod something-about-zot :before zot-debugging ((z zot))
   (format t ">>> ZOT debugging: calling SOMETHING-ABOUT-ZOT with ~S.~% z))

;;; and let's add some locking machinery to the class ZOT, on
;;; operation SOMETHING-ABOUT-LOCK.

(defvar zot-lock (make-lock))

(defmethod something-about-zot :before zot-debugging-and-locking ((z zot))
   (seize-lock zot-lock))

(defmethod something-about-zot :after zot-debugging-and-locking ((z zot))
   (release-lock zot-lock))

Now I have the guarantee that debugging and locking code will be
execute according to the CPL of the classes mentioned in the
definitions.

How you'd compile this, I have no idea. It almost looks like that the
"add method" procedure would become a O(n^2) operation, but I am just
guessing here.

I do not even have an idea whether you could implement this as a
METHOD-COMBINATION (which would require you to declare all you generic
functions as having that particular method combination, which is a
no-no in my idea of how thing should be done.

Now, what are the advantages of this scheme.

First of all, IMHO, it is CLOS like.

Second, it does not require you to either (a) change the "original"
class hierarchy in order to accommodate the new operations you may add
as :before or :after methods and (b) it allows you to keep all your
"extra" code separated and organized according to intent or to the
"aspect" (this is the keyword :) ) you want to deal with.

Finally, I am indebted to Barry Margolin for his suggestion to look
into Gregor Kiczales' "aspect oriented programming". Two days reading
their papers and playing around with AspectJ made me waste some more
time thinking about these issues in CL.
http://www.parc.xerox.com/spl/projects/aop

Of course there is much more under the stars, than what can be
contained between two parenthesis :)

Cheers

--
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - 06 68 10 03 17, fax. +39 - 06 68 80 79 26
http://www.parades.rm.cnr.it/~marcoxa



Thu, 18 Oct 2001 03:00:00 GMT  
 
 [ 22 post ]  Go to page: [1] [2]

 Relevant Pages 

1. AND now for something completely different

2. ISE interface - something completely different

3. And now, something completely different...

4. And now for something completely different...

5. python: something completely different

6. And Now for Something Completely Different! (was: Monty Pytho n (was: Freeware Python editor))

7. Now for something completely...

8. And now for something completely boneheaded...

9. And Now For Something Completely Similar !!!

10. A completely different Amrita has a Ruby interface

11. And now for something totaly different

12. Why is SetKey doing something different?

 

 
Powered by phpBB® Forum Software