As I understand it, precedence order is computed as filters then mixins the superclasses, and this sort order is applied "globally" to an object.
In other words:
Class create Base Class create Derived -superclass Base Derived d d info precedence ==> ::Derived ::Base ::xotcl::Object
Class create BaseMixin Base instmixin add BaseMixin d info precedence ==> ::BaseMixin ::Derived ::Base ::xotcl::Object
This behavior seems to violate encapsulation. BaseMixin is intended to intercept messages to Base. Derived doesn't know about BaseMixin and BaseMixin doesn't know about Derived, yet BaseMixin ends up being the first interceptor of messages to Derived.
Since BaseMixin is intended to modify the behavior of Base, it would be better BaseMixin preceeded Base (and only Base) in the sort order.
d info precedence ==> ::Derived ::BaseMixin ::Base ::xotcl::Object
The current behavior means you can't really add a mixin without understanding everywhere that the object being extended is used. Is there some benefit to the current behavior that I don't understand?
well, why do you think the behavior breaks encapsulation? Rather a behavior where mixin are not added in precedence before the class hierarchy, requires you to know about the interceptor and might break encapsulation. Consider you are omitting to use "next" in a method of Derived, for instance simply because this method exists nowhere else in the class hierarchy. If the mixin would be introduced after Derived, you would need to modify Derived to introduce a method of the same name on the mixin. If the mixin is always before the class hierarchy, as a base hierarchy developer you don't need to care for how a potential mixin is designed, because you can be sure once a call has reached your class, you can be sure, you cannot accidently introduce side-effects on mixins (maybe designed later by other developers).
Scott Gargash wrote:
As I understand it, precedence order is computed as filters then mixins the superclasses, and this sort order is applied "globally" to an object.
In other words:
Class create Base Class create Derived -superclass Base Derived d d info precedence ==> ::Derived ::Base ::xotcl::Object
Class create BaseMixin Base instmixin add BaseMixin d info precedence ==> ::BaseMixin ::Derived ::Base ::xotcl::Object
This behavior seems to violate encapsulation. BaseMixin is intended to intercept messages to Base. Derived doesn't know about BaseMixin and BaseMixin doesn't know about Derived, yet BaseMixin ends up being the first interceptor of messages to Derived.
Since BaseMixin is intended to modify the behavior of Base, it would be better BaseMixin preceeded Base (and only Base) in the sort order.
d info precedence ==> ::Derived ::BaseMixin ::Base ::xotcl::Object
The current behavior means you can't really add a mixin without understanding everywhere that the object being extended is used. Is there some benefit to the current behavior that I don't understand?
Xotcl mailing list Xotcl@alice.wu-wien.ac.at http://alice.wu-wien.ac.at/mailman/listinfo/xotcl
Uwe Zdun uwe.zdun@wu-wien.ac.at wrote on 08/29/2006 12:20:16 PM:
well, why do you think the behavior breaks encapsulation?
When I extend Base with BaseMixin, I'm altering the interface to Base. I can't safely alter the interface to Derived, because Base and BaseMixin don't necessarily know about Derived. In fact, since Derived is completely independent, it might not even exist when BaseMixin is defined. In gerneral BaseMixin can't be defined to work correctly with all possible derived classes, that set is open.
I believe when I add a mixin to Base I'm intercepting Base method invocations. When Derived invokes "next", it's logically chaining to Base. The Base may or may not have BaseMixin, but Derived shouldn't need to know. With BaseMixin intecepting Derived methods, BaseMixin needs to be made to work with Base & Derived. In practice, this seems like you can't safely use a mixin on any class thats also used as a superclass since the mixin will intercept any derived class's interface.
In practice, it's not possible to work with both. Assume BaseMixin is intercepting a leaf method with a leaf method: if BaseMixin invokes 'next' it will break Base and if it doesn't invoke 'next' it will break Derived.
The current behavior means I can't intercept a subset of the hierarchy, I have to intercept the whole thing. That means I can't encapsulate the use of a mixin; the mixin gets applied globally.
Rather a behavior where mixin are not added in precedence before the class hierarchy, requires you to know about the interceptor and might break encapsulation. Consider you are omitting to use "next" in a method of Derived, for instance simply because this method exists nowhere else in the class hierarchy. If the mixin would be introduced after Derived, you
would need to modify Derived to introduce a method of the same name on the mixin. If the mixin is always before the class hierarchy, as a base hierarchy developer you don't need to care for how a potential mixin is designed, because you can be sure once a call has reached your class, you can be sure, you cannot accidently introduce side-effects on mixins (maybe designed later by other developers).
To be clear, I agree that a mixin should take precedence over the class it's mixed into. I don't think it should take precedence over classes derived from the class it's mixed into. If I wanted to intercept method invocations on Derived, I would add a mixin to Derived but my explicit semantic is that I want to intercept methods on Base.
To ground this discussion a bit more in reality, my base class accesses hardware (Device). I then have a AppDevice class derived from the Device class that I use in my application. AppDevice will get a method, perform some processing and then invoke Device (via 'next').
So from the app point of view I have: AppDevice --> Device
For testing purposes I want to use a simulator. I created a DeviceSim class that reads and writes a buffer instead of the HW. I don't want my derived classes to know or care if it's using the simulator or the actual device. In either case the classes define leaf methods; the methods don't (and can't) chain.
In the test environment I want DeviceSim to intercept Device methods ("Device mixin add DeviceSim"): AppDevice --> DeviceSim --> Device
But what I get is: DeviceSim --> AppDevice --> Device
Since both Device and DeviceSim have leaf methods, adding DeviceSim as mixin to Device breaks AppDevice. It's the documented behavior, but it doesn't feel like the right behavior. The behavior I wanted (and what my code expressed) was to intercept Device methods not AppDevice methods. I don't see any way to limit interception to the subset of the hierarchy that I want.
Is what I'm trying to do unreasonable? It feels like I'm using the language features as intended, I just don't get the desired result.
Scott,
you can achieve the precedence you are wanting by adding multiple superclasses to Derived. In the forthcoming version (which will be named 1.5), you can even use "superclass add" like in the following example.
Class create Base Class create Derived -superclass Base Derived d puts [d info precedence] ;# ==> ::Derived ::Base ::xotcl::Object
Class create BaseMixin Derived superclass add BaseMixin puts [d info precedence] ;# ==> ::Derived ::BaseMixin ::Base ::xotcl::Object
the new release is already passing the regression test. i will do some more test with the aolserver+OpenAce and make it available rsn.
best regards -gustaf
Scott Gargash schrieb:
Class create Base Class create Derived -superclass Base Derived d d info precedence ==> ::Derived ::Base ::xotcl::Object
Class create BaseMixin Base instmixin add BaseMixin d info precedence ==> ::BaseMixin ::Derived ::Base ::xotcl::Object
This behavior seems to violate encapsulation. BaseMixin is intended to intercept messages to Base. Derived doesn't know about BaseMixin and BaseMixin doesn't know about Derived, yet BaseMixin ends up being the first interceptor of messages to Derived.
Since BaseMixin is intended to modify the behavior of Base, it would be better BaseMixin preceeded Base (and only Base) in the sort order.
d info precedence ==> ::Derived ::BaseMixin ::Base ::xotcl::Object
Gustaf Neumann neumann@wu-wien.ac.at wrote on 09/04/2006 03:50:42 AM:
Scott,
you can achieve the precedence you are wanting by adding multiple superclasses to Derived. In the forthcoming version (which will be named 1.5), you can even use "superclass add" like in the following example.
Thanks, but in my particular case I don't have knowledge of Derived, only Base. By that, I mean I'm actually adding BaseMixin (the HW simulator) to Base (the HW) before Derived has even been created. I'd like for Derived to remain ignorant of BaseMixin because BaseMixin only exists for testing purposes. I prefer to avoid having code paths that exist only for testing purposes. (Which mixins are particualrly well-suited for).
My current solution is to make Derived a mixin as well. This then forces it to the head of the precedence order regardless of the existence of BaseMixin so I don't have different code paths for BaseMixin vs. Base. It works, but it's not really correct from a design standpoint. Derived should be a derived class (it's a unique type), not a mixin.
This is what I meant about the current precedence order breaking encapsulation. I wish to modify a single class, but the precedence order makes it be global. In order to get things to work, I have to distribute knowledge of BaseMixin to places in the hierarchy that (I believe) shouldn't have to know or care.
What's the scenario where it's desireable for BaseMixin to be ahead of Derived?
Scott Gargash schrieb:
Thanks, but in my particular case I don't have knowledge of Derived, only Base. By that, I mean I'm actually adding BaseMixin (the HW simulator) to Base (the HW) before Derived has even been created. I'd like for Derived to remain ignorant of BaseMixin because BaseMixin only exists for testing purposes. I prefer to avoid having code paths that exist only for testing purposes. (Which mixins are particualrly well-suited for).
well, that are your requirements, but for others it might be different. Another question is, whether one would like to have the mixin added in for a certain class tree, or for each class tree. Using the multiple superclass approach allows/forces one to be specific (first case). it is however possible, to add on the xotcl level the superclass to the precedence order of all subclasses of the Base. Isn't this an option for you?
My current solution is to make Derived a mixin as well. This then forces it to the head of the precedence order regardless of the existence of BaseMixin so I don't have different code paths for BaseMixin vs. Base. It works, but it's not really correct from a design standpoint. Derived should be a derived class (it's a unique type), not a mixin.
as you said, if you want finer control, move more behavior into [inst]mixins. everything done with superclasses can be done with mixins alone as well, since the class structure is linearized anyhow. However, this leads to a unusual programming style.
This is what I meant about the current precedence order breaking encapsulation. I wish to modify a single class, but the precedence order makes it be global. In order to get things to work, I have to distribute knowledge of BaseMixin to places in the hierarchy that (I believe) shouldn't have to know or care.
What's the scenario where it's desireable for BaseMixin to be ahead of Derived?
One can argue in different directions. If one says, the class tree is the behavior, you do not want to know, in which class(es) exactly the methods are defined, but you want to decorate the behavior, the current mixin approach is appropriate.
Do you have a technical reason, why you want the instmixin BaseMixin in the precedence order between Base and Derived? I have developed many applications with mixins/instmixin, and i never had this need.
-gustaf
Gustaf Neumann neumann@wu-wien.ac.at wrote on 09/07/2006 10:24:35 AM:
Scott Gargash schrieb:
Thanks, but in my particular case I don't have knowledge of Derived, only Base. By that, I mean I'm actually adding BaseMixin (the HW simulator) to Base (the HW) before Derived has even been created. I'd
like for Derived to remain ignorant of BaseMixin because BaseMixin only exists for testing purposes. I prefer to avoid having code paths
that exist only for testing purposes. (Which mixins are particualrly well-suited for).
well, that are your requirements, but for others it might be different.
Agreed. Except I don't understand the scenario where the current behavior is desirable.
If mixins intercepted at the class they were mixed into, then you have the ability to choose precisely where you want the mixin to intercept:
That could be globally by adding the mixin at the head: ::Derived mixin add ::BaseMixin ==> ::BaseMixin ::Derived ::Base
Or at an intermediate location by adding the mixin lower in the stack ::Base mixin add ::BaseMixin ==> ::Derived ::BaseMixin ::Base
But the current definition only allows for the first case, irrespective of where the developer places the mixin. I can understand why someone might want to intercept Derived, but if that's what they want they should have to be explicit about it, no? Implicitly intercepting classes you don't have knowledge of seems to be asking for errors.
At least that's how I found out how this works...
Do you have a technical reason, why you want the instmixin BaseMixin in
the
precedence order between Base and Derived? I have developed many applications with mixins/instmixin, and i never had this need.
::Base ==> Class that manipulates HW device ::Derived ==> Derived class that adds application-specific methods to the HW device. ::BaseMixin ==> HW Simulator
Since BaseMixin simulates HW that Base would actually access, BaseMixin terminates (some) call chains (doesn't invoke 'next'). If BaseMixin is put in front of ::Derived, this keeps those ::Derived methods from being invoked. If BaseMixin invokes 'next', the HW is (incorrectly) accessed. If ::Derived is made aware of BaseMixin, then there's code in the release application dealing with BaseMixin, which won't exist in the release application (BaseMixin simulates HW for test purposes).
I'm sure that this is the appropriate precedence order. I'm not sure that mixins are the appropriate solution. Or rather, I was sure but since enough people don't share my belief that this is an issue I suspect I'm misusing it somehow.
Implementing a simulator as a mixin seemed like a good idea. The application has no reference to the simulator (::Derived -superclass ::Base), and the test harness can mixin the simulator as necessary (::Base mixin add ::BaseMixin) without touching the rest of the application. It just doesn't work.
I can accept that it doesn't work the way I want, but I'm still struggling with why it works the way it does. I don't see the advantage of always forcing the mixin to the head of the precedence, even ahead of classes that haven't been defined yet. My expectation was that when I added a mixin to a particular class, I would intercept methods at the point I added the mixin. Why is my expectation inappropriate?