Hello,
I've run into an issue when trying to remove a mixin from an object. It appears that removing a mixin from an object interacts with the method chaining, such that the "next" isn't invoked.
Here's an example:
Class Mixin1 Mixin1 instproc destroy {args} { puts "Mixin1 1: [my info mixin]" next my mixin delete ::Mixin1 puts "Mixin1 2: [my info mixin]" }
Class Mixin2 Mixin2 instproc destroy {args} { puts "Mixin2 1: [my info mixin]" my mixin delete ::Mixin2 puts "Mixin2 2: [my info mixin]" next }
Class Mixin3 Mixin3 instproc destroy {args} { puts "Mixin3 1: [my info mixin]" my mixin delete ::Mixin3 puts "Mixin3 2: [my info mixin]" next }
Object a
puts "a: [a info mixin]" a mixin set {::Mixin1 ::Mixin2 ::Mixin3} puts "a: [a info mixin]"
a destroy
And the output is
a: a: ::Mixin1 ::Mixin2 ::Mixin3 Mixin1 1: ::Mixin1 ::Mixin2 ::Mixin3 Mixin2 1: ::Mixin1 ::Mixin2 ::Mixin3 Mixin2 2: ::Mixin1 ::Mixin3 Mixin1 2: ::Mixin3
Because Mixin1 invokes "next" before "mixin delete", Mixin2's "destroy" method gets invoked. But since Mixin2 invokes "mixin delete" before "next", Mixin3's "destroy" method is not invoked, despite it remaining in the object's list of mixins.
This is not the behavior I would expect, and I can't find it documented anywhere. Is this the intended behavior? What should I do if I want to remove a mixin and then continue executing chained methods.
Scott
Notice The information in this message is confidential and may be legally privileged. It is intended solely for the addressee. Access to this message by anyone else is unauthorized. If you are not the intended recipient, any disclosure, copying or distribution of the message, or any action taken by you in reliance on it, is prohibited and may be unlawful. If you have received this message in error, please delete it and contact the sender immediately. Thank you.
Scott Gargash schrieb:
Hello,
I've run into an issue when trying to remove a mixin from an object. It appears that removing a mixin from an object interacts with the method chaining, such that the "next" isn't invoked.
scott,
in your example, you are trying to delete the currently active mixin from the mixin list (xotcl needs it currently to continue from that point in the mixin chain), and then you are deleting the object anyhow. Most probably, we should rise an error in such cases.
what is your intention, what lead you to this construction?
-gustaf
Here's an example:
Class Mixin1 Mixin1 instproc destroy {args} { puts "Mixin1 1: [my info mixin]" next my mixin delete ::Mixin1 puts "Mixin1 2: [my info mixin]" }
Class Mixin2 Mixin2 instproc destroy {args} { puts "Mixin2 1: [my info mixin]" my mixin delete ::Mixin2 puts "Mixin2 2: [my info mixin]" next }
Class Mixin3 Mixin3 instproc destroy {args} { puts "Mixin3 1: [my info mixin]" my mixin delete ::Mixin3 puts "Mixin3 2: [my info mixin]" next }
Object a
puts "a: [a info mixin]" a mixin set {::Mixin1 ::Mixin2 ::Mixin3} puts "a: [a info mixin]"
a destroy
And the output is
a: a: ::Mixin1 ::Mixin2 ::Mixin3 Mixin1 1: ::Mixin1 ::Mixin2 ::Mixin3 Mixin2 1: ::Mixin1 ::Mixin2 ::Mixin3 Mixin2 2: ::Mixin1 ::Mixin3 Mixin1 2: ::Mixin3
Because Mixin1 invokes "next" before "mixin delete", Mixin2's "destroy" method gets invoked. But since Mixin2 invokes "mixin delete" before "next", Mixin3's "destroy" method is not invoked, despite it remaining in the object's list of mixins.
This is not the behavior I would expect, and I can't find it documented anywhere. Is this the intended behavior? What should I do if I want to remove a mixin and then continue executing chained methods.
Scott
*Notice* The information in this message is confidential and may be legally privileged. It is intended solely for the addressee. Access to this message by anyone else is unauthorized. If you are not the intended recipient, any disclosure, copying or distribution of the message, or any action taken by you in reliance on it, is prohibited and may be unlawful. If you have received this message in error, please delete it and contact the sender immediately. Thank you.
Xotcl mailing list Xotcl@alice.wu-wien.ac.at http://alice.wu-wien.ac.at/mailman/listinfo/xotcl
Gustaf Neumann neumann@wu-wien.ac.at wrote on 05/06/2006 05:10:24 PM:
Scott Gargash schrieb:
Hello,
I've run into an issue when trying to remove a mixin from an object. It appears that removing a mixin from an object interacts with the method chaining, such that the "next" isn't invoked.
scott,
in your example, you are trying to delete the currently active mixin from the mixin list (xotcl needs it currently to continue from that point in the mixin chain), and then you are deleting the object anyhow. Most probably, we should rise an error in such cases.
what is your intention, what lead you to this construction?
In my particular case, I'm manipulating a graph. Some nodes have mixins that are used to notify other connected nodes. When a node (with this particular mixin) is destroyed, it notifies connected nodes that it's being disconnected. Another mixin class later in the chain intercepts the destroy call and moves the node back to a pool (the nodes manipulate a fixed pool of hardware resources, so they need to be tracked and reused). Since "move" reinvokes the destructor, if I don't remove the notification mixin from the object, connected nodes are doubly notified (bad), but when I remove the mixin, I don't get the correct chaining behavior and resource reclamation.
BTW, this will happen with any method, not just "destroy". Is it not considered acceptable to ever remove a mixin class? When is it valid to remove a mixin if removing it corrupts the method chaining?
Scott
BTW, this will happen with any method, not just "destroy".
i know.
Is it not considered acceptable to ever remove a mixin class?
When is it valid to remove a mixin if removing it corrupts the method chaining?
removing a mixin class from the mixin list does not "corrupt" the chain in the sense that there is a memory corruption etc. The current implementation does not allow to remove ***active*** mixin classes, this are the classes of which the method is currently executing.
what does this mean, assume there are classes M1, M2, M3, all of the form
Class M2 M2 instproc foo {} { BEFORE next AFTER }
Object o -mixin {M1 M2 M3}
when method foo of M2 is called, M2 is the active call. If between the invocation of foo of M2 and "next" the mixin class M2 is removed for o, "next" has the problem to find the continuation (next searches the current mixin classes of o for the currently executing Class). If this class is not found (your example), the list is searched to the end and no other Mixin classes are searched.
So, the only restriction is that you are not allowed to remove the mixin from o in the BEFORE part of M2. If you remove M2 from the BEFORE part of M1, there won't be a problem, since foo of M2 won't be called. If you remove M2 in AFTER of M2 foo, there would not be a problem either, since "next" was alreday executed.
It looks quite easy to give a reasonable error message, when the currently active mixin class is deleted, it will be more expensive to handle this problem in a friendly way.
Hope, this explains, what is happening.
-gustaf
PS: wouln't be conditional mixins a solution for your problem?
Gustaf Neumann neumann@wu-wien.ac.at wrote on 05/08/2006 02:15:41 PM:
So, the only restriction is that you are not allowed to remove the mixin from o in the BEFORE part of M2. If you remove M2 from the BEFORE part of M1, there won't be a problem, since foo of M2 won't be called. If you remove M2 in AFTER of M2 foo, there would not be a problem either, since "next" was alreday executed.
Your explanation is certainly consistent with the behavior I observed. Is this documented somewhere? It surprised me.
PS: wouln't be conditional mixins a solution for your problem?
I don't know. What do you mean by "conditional mixin"?
Scott
Scott Gargash schrieb:
Is this documented somewhere? It surprised me.
in the source (joke).
PS: wouln't be conditional mixins a solution for your problem?
I don't know. What do you mean by "conditional mixin"?
mixins with guards. see following example. -gustaf
Class Fly -instproc fly {} {puts "[my signature]: yippee, fly like an eagle!"} Class Sing -instproc sing {} {puts "[my signature]: what a difference a day make"}
Class Animal -parameter age Animal instproc unknown args { puts "[my signature]: how should i $args?"} Animal instproc signature {} { return "[self] [my info class] ([my age] years)" }
Class Bird -superclass Animal Class Penguine -superclass Bird Class Parrot -superclass Bird Class Duck -superclass Bird
Parrot tweedy -age 1 Penguine pingo -age 5 Duck donald -age 4 Parrot lora -age 6
Bird instmixin {{Fly -guard {[my age]>2 && ![my istype Penguine]}} Sing} foreach bird [Animal allinstances] { $bird fly }
Scott
Gustaf Neumann neumann@wu-wien.ac.at wrote on 05/08/2006 02:15:41 PM:
BTW, this will happen with any method, not just "destroy".
i know.
Is it not considered acceptable to ever remove a mixin class?
When is it valid to remove a mixin if removing it corrupts the method chaining?
removing a mixin class from the mixin list does not "corrupt" the chain in the sense that there is a memory corruption etc. The current implementation does not allow to remove ***active*** mixin classes, this are the classes of which the method is currently executing.
I did a little more experimenting, and it's a little stronger than that. You also can't remove a mixin from anything invoked by the active mixin. I.e., you can't forward the "mixin delete" to an object of another class. If the mixin to be deleted is present in the call stack, it appears that the mixin can't be deleted. Not that I expected it to work, but it seems worth noting.
It looks quite easy to give a reasonable error message, when the currently active mixin class is deleted, it will be more expensive to handle this problem in a friendly way.
Is the expense something that would be encountered on every method invocation, or would it only be paid when deleting a mixin?
I really ought to spend some time with the source...
Scott
Scott Gargash schrieb:
I did a little more experimenting, and it's a little stronger than that. You also can't remove a mixin from anything invoked by the active mixin.
in the BEFORE part (this concerns everything that directly on indirectly).
I.e., you can't forward the "mixin delete" to an object of another class. If the mixin to be deleted is present in the call stack, it appears that the mixin can't be deleted. Not that I expected it to work, but it seems worth noting.
if you have a case that goes beyond "do not remove actvive mixins from the before part of active mixin classes"), please send an example.
It looks quite easy to give a reasonable error message, when the currently active mixin class is deleted, it will be more expensive to handle this
problem in
a friendly way.
Is the expense something that would be encountered on every method invocation, or would it only be paid when deleting a mixin?
a simple solution, not costing anything, would be to restart the mixin chain when the active mixin is removed. This would work with your example but might lead to unexpeted cases in other situations and is no big improvement to the current situation.
We have to deal with the following cases when we have e.g. a precedence order M1 M2 M3 M4, and the following happens in the BEFORE part of M2:
a chain is set to: M1 M3 M4 (your case, you want to continue with M3) b chain is set to: M1 M4 defensible M4 c chain is set to: M2 M1 M3 M4 under current semantics, continue with M1 d chain is set to: M4 M2 M3 M1 continue with M3, M4 will never by invoked
The general problem is pretty similar to the "immediate" and "logical" update view in logic languages, where the clause base is modified during the execution of a goal. The logical view says that modifications are ignored during execution and only apply to new invocations (similar to SQL), wheras the immediate view tries to give defensible semantics for the active invocations (what you are trying). It has been a while since i worked on this stuff, but it goes back to Lindholm and Richard O'Keefe (ROK) from 87, */Efficient implementation of a defensible semantics for dynamic Prolog code/* (unfortunately, could not find this fine paper on the net). In essence, this says that one can only get declarative semantics from the logical update view, which is not acceptible for programmers that want to change the dynamic structure, preferring the immediate view, which is more efficiently in terms of memory and computation. Therefore most Prolog Systems implement the dynamic update view.
So, the underlying problem is well known and not a matter of a trivial hack. The situation with xotcl mixins/filters is in some respects more complicated since we are not dealing with adding/removing clauses but we have to deal with reordering, adding or removing items to instance specific precedence chains; we have as well transitive mixin-chains.
Currently, we can even construct loops by reordering the mixins in the BEFORE part, which i consider as a programmers bug.
The most promissing approach not mentioned yet is to develop a c-level implementation of mixin/instmixin/filter/instfilter delete, which simply flags the entry in the chain as deleted.....
cheers -gustaf
Gustaf Neumann neumann@wu-wien.ac.at wrote on 05/09/2006 03:54:49 AM:
Scott Gargash schrieb:
I.e., you can't forward the "mixin delete" to an object of another class. If the mixin to be deleted is present in the call stack, it appears that the mixin can't be deleted. Not that I expected it to work, but it seems worth noting.
if you have a case that goes beyond "do not remove actvive mixins from the before part of active mixin classes"), please send an example.
No, the same conditions apply. It's just that the BEFORE part includes anything invoked indirectly by the BEFORE part. Sometimes you don't have knowledge of what some other object will do when invoked, and it can be a subtle bug.
We have to deal with the following cases when we have e.g. a precedence order M1 M2 M3 M4, and the following happens in the BEFORE part of M2:
a chain is set to: M1 M3 M4 (your case, you want to continue with M3) b chain is set to: M1 M4 defensible M4 c chain is set to: M2 M1 M3 M4 under current semantics, continue with M1 d chain is set to: M4 M2 M3 M1 continue with M3, M4 will never by invoked
Yup, that's exactly the behavior I (naively) had expected.
But to be clear, what is the current behavior of 'next' for each of these cases? To unwind the call stack? When can the updated mixin list be considered to have taken effect?
The general problem is pretty similar to the "immediate" and "logical" update view in logic languages,
Good analogy, that makes sense.
but it goes back to Lindholm and Richard O'Keefe (ROK) from 87, */Efficient implementation of a defensible semantics for dynamic Prolog code/* (unfortunately, could not find this fine paper on the net).
There's also a brief discussion of this in O'Keefe's book "The Craft of Prolog".
The most promissing approach not mentioned yet is to develop a c-level implementation of mixin/instmixin/filter/instfilter delete, which simply flags the entry in the chain as deleted.....
Interestingly, that's how I assumed it was implemented, which is probably why I was surprised.
Scott
Scott Gargash schrieb:
We have to deal with the following cases when we have e.g. a precedence order M1 M2 M3 M4, and the following happens in the BEFORE part of M2:
a chain is set to: M1 M3 M4 (your case, you want to continue
with M3)
b chain is set to: M1 M4 defensible M4 c chain is set to: M2 M1 M3 M4 under current semantics,
continue with M1
d chain is set to: M4 M2 M3 M1 continue with M3, M4 will never by invoked
Yup, that's exactly the behavior I (naively) had expected.
But to be clear, what is the current behavior of 'next' for each of these cases? To unwind the call stack? When can the updated mixin list be considered to have taken effect?
the current implementation the mixin command is an immediate update (it replaces the old mixin chain), "next" searches for the current class (M2), but does not find it, and assumes that it is at the end of the mixin chain.
The most promissing approach not mentioned yet is to develop a c-level implementation of mixin/instmixin/filter/instfilter delete, which simply flags the entry in the chain as deleted.....
Interestingly, that's how I assumed it was implemented, which is probably why I was surprised.
i will check this option out in more detail (earliest this weekend). so, don't hold your breath...
-gustaf