Folks -
I'm working slowly through some of the traits test cases, and some of them simply make no sense to me. Currently, I'm in TraitCompositionTest>>testInvalidComposition and most of it seems very odd, for example: self should: [self t1 @ { (#a -> #b) } @ { (#x -> #y) }] raise: TraitCompositionException. This currently signals a TraitCompositionException 'Invalid trait exclusion. Aliases have to be specified before exclusions.' but note that the above doesn't *have* any exclusions, so what's the point here? From my understanding of traits, I would rather assume that: self assert: (self t1 @ { (#a -> #b) } @ { (#x -> #y) }) = (self t1 @ {#a -> #b. #x -> #y}). Same goes for exclusions, e.g., instead of: self should: [self t1 - { #a } - { #b }] raise: TraitCompositionException. I would rather claim that: self assert: (self t1 - {#a} - {#b}) = (self t1 - {#a. #b}). Lastly, it's not clear to me why the requirement that aliases have to be specified before exclusions even exists. It seems pretty clear what the expected outcome is. Or is this just to keep a bit of conceptual clarity, i.e., that aliasing (@ operator) comes always before exclusions (- operator). From the implementation point of view it's actually simpler to support arbitrary compositions and if you're into this kind of stuff I see no reason to force superficial rules on you :-) Oh, and one more issue: I'm starting to question the idea that "conflicting" trait methods should create a trait conflict. It seems to me that "last one wins" would be more useful, i.e., if you have two traits t1 and t2 and use them via t1+t2 you get t2's version; if you use them via t2+t1 you get t1's version. You're always free to override the version if you don't like the result but I think that "last one wins" will be a useful outcome in more cases than raising an error :-) Any comments are greatly welcome. Cheers, - Andreas |
On Mon, Dec 07, 2009 at 09:01:53PM -0800, Andreas Raab wrote:
> Any comments are greatly welcome. I bet this has all been revised a bit in Pharo... -- Matthew Fulmer (a.k.a. Tapple) |
Matthew Fulmer wrote:
> On Mon, Dec 07, 2009 at 09:01:53PM -0800, Andreas Raab wrote: >> Any comments are greatly welcome. > > I bet this has all been revised a bit in Pharo... What did you bet? I checked the tests and they're the same so you loose :-) Cheers, - Andreas |
In reply to this post by Andreas.Raab
2009/12/8 Andreas Raab <[hidden email]>:
> Folks - > > I'm working slowly through some of the traits test cases, and some of them > simply make no sense to me. Currently, I'm in > TraitCompositionTest>>testInvalidComposition and most of it seems very odd, > for example: > > self should: [self t1 @ { (#a -> #b) } @ { (#x -> #y) }] > raise: TraitCompositionException. > > This currently signals a TraitCompositionException 'Invalid trait exclusion. > Aliases have to be specified before exclusions.' but note that the above > doesn't *have* any exclusions, so what's the point here? From my > understanding of traits, I would rather assume that: > > self assert: (self t1 @ { (#a -> #b) } @ { (#x -> #y) }) > = (self t1 @ {#a -> #b. #x -> #y}). > > Same goes for exclusions, e.g., instead of: > > self should: [self t1 - { #a } - { #b }] raise: > TraitCompositionException. > > I would rather claim that: > > self assert: (self t1 - {#a} - {#b}) = (self t1 - {#a. #b}). > > Lastly, it's not clear to me why the requirement that aliases have to be > specified before exclusions even exists. It seems pretty clear what the > expected outcome is. Or is this just to keep a bit of conceptual clarity, > i.e., that aliasing (@ operator) comes always before exclusions (- > operator). From the implementation point of view it's actually simpler to > support arbitrary compositions and if you're into this kind of stuff I see > no reason to force superficial rules on you :-) > > Oh, and one more issue: I'm starting to question the idea that "conflicting" > trait methods should create a trait conflict. It seems to me that "last one > wins" would be more useful, i.e., if you have two traits t1 and t2 and use > them via t1+t2 you get t2's version; if you use them via t2+t1 you get t1's > version. You're always free to override the version if you don't like the > result but I think that "last one wins" will be a useful outcome in more > cases than raising an error :-) > T1+T2 should mean 'apply T1 then T2', but not 'apply combination of T1 and T2'. Its much easier to implement as well as easy to understand & follow. Also, in Pharo list i suggested extension to exclusion operator: in addition to: T1 - selectorsArray "{ #sel1. #sel2 }" also have: T1 - T2 which should transparently behave as: T1 - (T2 selectors) and mean: apply all from T1 , except selectors in T2. Btw, i am curious about one thing: Since traits allowed to hold method for both instance side and class side. Then suppose my trait having: #isel1 #isel2 at instance side, and #csel1 #csel2 at class side. Now, how i can specify a composition, which applies my trait, except #isel1 and #csel2 ? So, final class should have #isel2 method at instance side and #csel2 at class side, but not #isel1 and not #csel2. And what is a correct syntax for defining such composition in class definition? > Any comments are greatly welcome. > > Cheers, > - Andreas -- Best regards, Igor Stasenko AKA sig. |
Igor Stasenko wrote:
> T1+T2 > should mean 'apply T1 then T2', but not 'apply combination of T1 and T2'. > Its much easier to implement as well as easy to understand & follow. That's what I'm thinking as well. > Also, in Pharo list i suggested extension to exclusion operator: > > in addition to: > T1 - selectorsArray "{ #sel1. #sel2 }" > > also have: > > T1 - T2 > > which should transparently behave as: > > T1 - (T2 selectors) > > and mean: apply all from T1 , except selectors in T2. I'll leave this as an exercise for the interested reader :) > Btw, i am curious about one thing: > Since traits allowed to hold method for both instance side and class side. Ah, yes. Classes and metaclasses and traits. Last time we had this discussion it was pretty clear that this whole area is not well-understood. That you can only apply a trait without class-side methods to a class for example, causes all sorts of irritation when you later add a class method to the trait, thereby making it non-applicable to compositions where it's already used. It's inconsistent at least (broken as far as I'm concerned since you can create compositions that can't be recreated). > Now, how i can specify a composition, which applies my trait, except > #isel1 and #csel2 ? > So, final class should have #isel2 method at instance side and > #csel2 at class side, but not #isel1 and not #csel2. > And what is a correct syntax for defining such composition in class definition? Yes, that's just more inconsistencies resulting from the same basic problem. Cheers, - Andreas |
2009/12/8 Andreas Raab <[hidden email]>:
> Igor Stasenko wrote: >> >> T1+T2 >> should mean 'apply T1 then T2', but not 'apply combination of T1 and T2'. >> Its much easier to implement as well as easy to understand & follow. > > That's what I'm thinking as well. > >> Also, in Pharo list i suggested extension to exclusion operator: >> >> in addition to: >> T1 - selectorsArray "{ #sel1. #sel2 }" >> >> also have: >> >> T1 - T2 >> >> which should transparently behave as: >> >> T1 - (T2 selectors) >> >> and mean: apply all from T1 , except selectors in T2. > > I'll leave this as an exercise for the interested reader :) > >> Btw, i am curious about one thing: >> Since traits allowed to hold method for both instance side and class side. > > Ah, yes. Classes and metaclasses and traits. Last time we had this > discussion it was pretty clear that this whole area is not well-understood. > That you can only apply a trait without class-side methods to a class for > example, causes all sorts of irritation when you later add a class method to > the trait, thereby making it non-applicable to compositions where it's > already used. It's inconsistent at least (broken as far as I'm concerned > since you can create compositions that can't be recreated). > >> Now, how i can specify a composition, which applies my trait, except >> #isel1 and #csel2 ? >> So, final class should have #isel2 method at instance side and >> #csel2 at class side, but not #isel1 and not #csel2. >> And what is a correct syntax for defining such composition in class >> definition? > > Yes, that's just more inconsistencies resulting from the same basic problem. > > Cheers, > - Andreas > Since classInstVarNames are defined in a separate declaration, I do not see why it couldn't be so for Traits. This would lead to some simplications. A possible POV is to handle #class as an ordinary message. It could eventually return any object (squeak specific implementation apart). So why put requirements on the protocol of object answered by #class and not on the protocol answered by #asInteger ? It is well known the second is a Boolean after all... If we don't put protocol requirements on self class, we then do not need any class side Trait, do we ? (I mean they could just be ordinary Traits). Nicolas |
2009/12/8 Nicolas Cellier <[hidden email]>:
> 2009/12/8 Andreas Raab <[hidden email]>: >> Igor Stasenko wrote: >>> >>> T1+T2 >>> should mean 'apply T1 then T2', but not 'apply combination of T1 and T2'. >>> Its much easier to implement as well as easy to understand & follow. >> >> That's what I'm thinking as well. >> >>> Also, in Pharo list i suggested extension to exclusion operator: >>> >>> in addition to: >>> T1 - selectorsArray "{ #sel1. #sel2 }" >>> >>> also have: >>> >>> T1 - T2 >>> >>> which should transparently behave as: >>> >>> T1 - (T2 selectors) >>> >>> and mean: apply all from T1 , except selectors in T2. >> >> I'll leave this as an exercise for the interested reader :) >> >>> Btw, i am curious about one thing: >>> Since traits allowed to hold method for both instance side and class side. >> >> Ah, yes. Classes and metaclasses and traits. Last time we had this >> discussion it was pretty clear that this whole area is not well-understood. >> That you can only apply a trait without class-side methods to a class for >> example, causes all sorts of irritation when you later add a class method to >> the trait, thereby making it non-applicable to compositions where it's >> already used. It's inconsistent at least (broken as far as I'm concerned >> since you can create compositions that can't be recreated). >> >>> Now, how i can specify a composition, which applies my trait, except >>> #isel1 and #csel2 ? >>> So, final class should have #isel2 method at instance side and >>> #csel2 at class side, but not #isel1 and not #csel2. >>> And what is a correct syntax for defining such composition in class >>> definition? >> >> Yes, that's just more inconsistencies resulting from the same basic problem. >> >> Cheers, >> - Andreas >> > > Since classInstVarNames are defined in a separate declaration, I do > not see why it couldn't be so for Traits. > This would lead to some simplications. > A possible POV is to handle #class as an ordinary message. It could > eventually return any object (squeak specific implementation apart). > So why put requirements on the protocol of object answered by #class > and not on the protocol answered by #asInteger ? It is well known the > second is a Boolean after all... If we don't put protocol requirements > on self class, we then do not need any class side Trait, do we ? (I > mean they could just be ordinary Traits). > IMO ideally, trait should NOT care where it applied to (be it class, metaclass or any other object). It should expect that object supporting a certain Behavior-based protocol (like #setTraitComposition:) but nothing more than that. Naturally, trait could be seen as a partial behavior, and so, it should be applicable to just behavior only, not to two behaviors at once (class/metaclass). I think this i a clear case, where we could have a much less complex implementation by sacrificing the 'class side' view of trait (ClassTrait). Because, one could always create a two separate traits (TApple , TAppleClassSide) and apply each one independently: Apple uses: TApple.. Apple class uses: TAppleClassSide. -- Best regards, Igor Stasenko AKA sig. |
2009/12/8 Igor Stasenko <[hidden email]>:
> 2009/12/8 Nicolas Cellier <[hidden email]>: >> 2009/12/8 Andreas Raab <[hidden email]>: >>> Igor Stasenko wrote: >>>> >>>> T1+T2 >>>> should mean 'apply T1 then T2', but not 'apply combination of T1 and T2'. >>>> Its much easier to implement as well as easy to understand & follow. >>> >>> That's what I'm thinking as well. >>> >>>> Also, in Pharo list i suggested extension to exclusion operator: >>>> >>>> in addition to: >>>> T1 - selectorsArray "{ #sel1. #sel2 }" >>>> >>>> also have: >>>> >>>> T1 - T2 >>>> >>>> which should transparently behave as: >>>> >>>> T1 - (T2 selectors) >>>> >>>> and mean: apply all from T1 , except selectors in T2. >>> >>> I'll leave this as an exercise for the interested reader :) >>> >>>> Btw, i am curious about one thing: >>>> Since traits allowed to hold method for both instance side and class side. >>> >>> Ah, yes. Classes and metaclasses and traits. Last time we had this >>> discussion it was pretty clear that this whole area is not well-understood. >>> That you can only apply a trait without class-side methods to a class for >>> example, causes all sorts of irritation when you later add a class method to >>> the trait, thereby making it non-applicable to compositions where it's >>> already used. It's inconsistent at least (broken as far as I'm concerned >>> since you can create compositions that can't be recreated). >>> >>>> Now, how i can specify a composition, which applies my trait, except >>>> #isel1 and #csel2 ? >>>> So, final class should have #isel2 method at instance side and >>>> #csel2 at class side, but not #isel1 and not #csel2. >>>> And what is a correct syntax for defining such composition in class >>>> definition? >>> >>> Yes, that's just more inconsistencies resulting from the same basic problem. >>> >>> Cheers, >>> - Andreas >>> >> >> Since classInstVarNames are defined in a separate declaration, I do >> not see why it couldn't be so for Traits. >> This would lead to some simplications. >> A possible POV is to handle #class as an ordinary message. It could >> eventually return any object (squeak specific implementation apart). >> So why put requirements on the protocol of object answered by #class >> and not on the protocol answered by #asInteger ? It is well known the >> second is a Boolean after all... If we don't put protocol requirements >> on self class, we then do not need any class side Trait, do we ? (I >> mean they could just be ordinary Traits). >> > > IMO ideally, trait should NOT care where it applied to (be it class, > metaclass or any other object). > It should expect that object supporting a certain Behavior-based > protocol (like #setTraitComposition:) > but nothing more than that. > > Naturally, trait could be seen as a partial behavior, and so, it > should be applicable to just behavior only, > not to two behaviors at once (class/metaclass). > > I think this i a clear case, where we could have a much less complex > implementation by sacrificing the 'class side' view of trait > (ClassTrait). > > Because, one could always create a two separate traits (TApple , > TAppleClassSide) and apply each one independently: > > Apple uses: TApple.. > Apple class uses: TAppleClassSide. > > fully agree, that's what I tried to express Nicolas > > -- > Best regards, > Igor Stasenko AKA sig. > > |
In reply to this post by Andreas.Raab
On 7-Dec-09, at 9:01 PM, Andreas Raab wrote: > Oh, and one more issue: I'm starting to question the idea that > "conflicting" trait methods should create a trait conflict. It seems > to me that "last one wins" would be more useful, i.e., if you have > two traits t1 and t2 and use them via t1+t2 you get t2's version; if > you use them via t2+t1 you get t1's version. You're always free to > override the version if you don't like the result but I think that > "last one wins" will be a useful outcome in more cases than raising > an error :-) That wouldn't be Traits, it would be Mixins. Maybe you'd rather have Mixins? It would be interesting to compare Strongtalk's collection classes to the paper about refactoring Collections to use Traits. Colin |
2009/12/8 Colin Putney <[hidden email]>:
> > On 7-Dec-09, at 9:01 PM, Andreas Raab wrote: > >> Oh, and one more issue: I'm starting to question the idea that >> "conflicting" trait methods should create a trait conflict. It seems to me >> that "last one wins" would be more useful, i.e., if you have two traits t1 >> and t2 and use them via t1+t2 you get t2's version; if you use them via >> t2+t1 you get t1's version. You're always free to override the version if >> you don't like the result but I think that "last one wins" will be a useful >> outcome in more cases than raising an error :-) > > That wouldn't be Traits, it would be Mixins. Maybe you'd rather have Mixins? > It would be interesting to compare Strongtalk's collection classes to the > paper about refactoring Collections to use Traits. > Why? Can composition be an ordered collection of traits, so, they applied in sequencial order to class? Mixins use an inheritance chaining to achieve same effect. But traits don't using inheritance, and that's, i think, the main difference between them and mixins, but not the order of composition. > Colin > > -- Best regards, Igor Stasenko AKA sig. |
On 8-Dec-09, at 9:34 AM, Igor Stasenko wrote: > > Why? > Can composition be an ordered collection of traits, so, they applied > in sequencial order to class? > Mixins use an inheritance chaining to achieve same effect. > But traits don't using inheritance, and that's, i think, the main > difference between them and mixins, but not the order of composition. One of the defining features of Traits is that composition is commutative. The order doesn't matter because the composition of any two traits is an XOR operation on the methods that each Trait defines. Any method that is defined in both the Traits does not show up in the composition. The fact that Traits don't use inheritance chaining is a consequence of this definition, not the defining feature its self. Colin |
2009/12/8 Colin Putney <[hidden email]>:
> > On 8-Dec-09, at 9:34 AM, Igor Stasenko wrote: >> >> Why? >> Can composition be an ordered collection of traits, so, they applied >> in sequencial order to class? >> Mixins use an inheritance chaining to achieve same effect. >> But traits don't using inheritance, and that's, i think, the main >> difference between them and mixins, but not the order of composition. > > One of the defining features of Traits is that composition is commutative. > The order doesn't matter because the composition of any two traits is an XOR > operation on the methods that each Trait defines. Any method that is defined > in both the Traits does not show up in the composition. > It shows up. Trait named: #TFoo uses: {} category: 'WeirdExperiment' Trait named: #TBar uses: {} category: 'WeirdExperiment' now add method #foo to both of them. Object subclass: #TFooBar uses: TFoo + TBar instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'WeirdExperiment' browser shows: foo self traitConflict TFooBar methodDict a MethodDictionary(#foo->(TFooBar>>#foo "a CompiledMethod(1916)") ) IMO this is a minor matter, not the design cornerstone. If two or more methods conflicting there can be following choices: - take a first one - take the last one - take none (yours ) - include method with error signaling (current implementation). I think that first two is much more useful in practice, not the last two ones. As to me, there is not much sense in making them commutative, since when you have a conflict, you are doing something: t1 - {#a} + t2 or t2 - {#a} + t1 which now is a mix of non-commutative #- operator with #+ . or can i assume that following should always yield true: t1 + t2 - {#a} = t1 - {#a} + t2 ? > The fact that Traits don't use inheritance chaining is a consequence of this > definition, not the defining feature its self. > > Colin > > -- Best regards, Igor Stasenko AKA sig. |
On Tue, Dec 8, 2009 at 11:49 AM, Igor Stasenko <[hidden email]> wrote:
but as an error. There isn't any way of removing a method. Were foo inherited from some superclass then unless the error method is generated there would be no indication that there was a conflict between the two traits defining foo. So traitError is a more specific version of shouldNotImplement.
foo self shouldNotImplement is not as descriptive (it could be expressing the class author's intent) as foo self traitConflict
but both are equivalent; they raise run-time errors saying "this method shouldn't be here". That's not the same as saying the conflict shows up as a useful method; it doesn't.
|
In reply to this post by Colin Putney
Colin Putney wrote:
> One of the defining features of Traits is that composition is > commutative. The order doesn't matter because the composition of any two > traits is an XOR operation on the methods that each Trait defines. Any > method that is defined in both the Traits does not show up in the > composition. Wh...wh...wh..what? "any method that is defined in both traits does not show up in the composition"??? Can you provide any evidence for this? That is not my understanding of neither intention nor reality. Cheers, - Andreas |
In reply to this post by Eliot Miranda-2
Eliot Miranda wrote:
> but as an error. There isn't any way of removing a method. Were foo > inherited from some superclass then unless the error method is generated > there would be no indication that there was a conflict between the two > traits defining foo. So traitError is a more specific version of > shouldNotImplement. > > foo > self shouldNotImplement > > is not as descriptive (it could be expressing the class author's intent) as > > foo > self traitConflict > > but both are equivalent; they raise run-time errors saying "this method > shouldn't be here". That's not the same as saying the conflict shows up > as a useful method; it doesn't. My understanding was that #traitConflict is essentially equivalent to a "self shouldBeImplemented", i.e., an indicator that manual work is needed to resolve the conflict. That's why I'm saying that "last one wins" would be a useful default more often than raising an error. See my previous comment - the idea that traits are (by design) intended to represent an XOR of methods is complete news to me. It'd be good if someone could back this up. BTW, I really don't think there is much difference between mixins and traits; all forms of MI are fairly equivalent. Cheers, - Andreas |
Hi all!
Andreas Raab wrote: > BTW, I really don't think there is much difference between mixins and > traits; all forms of MI are fairly equivalent. > > Cheers, > - Andreas As most of us I was also very positive when Traits first came to the scene. Now, I am a lot more jaded and think "well, sure, but are they worth it?". Given Andreas' statement above - wouldn't it be *much* cooler to evolve Smalltalk along the axis of composition instead of the axis of inheritance? I have always thought that having better mechanisms for delegation would be awesome, and would in most ways be much more powerful than inheritance (in whichever form). For example, what if one could declare that for class Foo (having ivars x, y, z) any message that would result in a DNU would instead be "delegated" to ivar x (and then y if no lookup is found in x either). This would be equivalent to tons of messages in Foo like: Foo>>name ^(x respondsTo: #name) ifTrue: [x name] ifFalse: [y name] (well, kinda, you get the picture - but also, taking care of x/y returning "self" in which case we probably should return the "Foo self" instead etc) In many ways the mechanism I am describing is "built into" languages like self and Slate (I think). IMHO the above is very useful and would allow fine grained composition similar to Traits but in a dynamic more object centric fashion. regards, Göran |
Hi Göran -
Unbelievable. You basically just described the very idea that started this whole traits thing. When Nathanael worked with us at Disney, the traits direction came out of discussions where we wanted him to think precisely along the lines you're describing. My original thoughts on this matter was that I wanted to have something more like (biological) cells - entities that are made up of smaller things (objects), that have an inside and an outside (made up of other objects instead of abstractions like the interface/implementation distinction), that forward signals (messages) to the appropriate receptors etc. What you're describing is very much in line with my thoughts at that point which also included more actor-like structures (some of which was later realized in Tweak and once I found E got an even stronger version in Croquet). Cheers, - Andreas Göran Krampe wrote: > Hi all! > > Andreas Raab wrote: >> BTW, I really don't think there is much difference between mixins and >> traits; all forms of MI are fairly equivalent. >> >> Cheers, >> - Andreas > > As most of us I was also very positive when Traits first came to the > scene. Now, I am a lot more jaded and think "well, sure, but are they > worth it?". > > Given Andreas' statement above - wouldn't it be *much* cooler to evolve > Smalltalk along the axis of composition instead of the axis of inheritance? > > I have always thought that having better mechanisms for delegation would > be awesome, and would in most ways be much more powerful than > inheritance (in whichever form). > > For example, what if one could declare that for class Foo (having ivars > x, y, z) any message that would result in a DNU would instead be > "delegated" to ivar x (and then y if no lookup is found in x either). > > This would be equivalent to tons of messages in Foo like: > > Foo>>name > ^(x respondsTo: #name) ifTrue: [x name] ifFalse: [y name] > > (well, kinda, you get the picture - but also, taking care of x/y > returning "self" in which case we probably should return the "Foo self" > instead etc) > > In many ways the mechanism I am describing is "built into" languages > like self and Slate (I think). > > IMHO the above is very useful and would allow fine grained composition > similar to Traits but in a dynamic more object centric fashion. > > regards, Göran > > > |
On Dec 8, 2009, at 4:08 PM, Andreas Raab wrote: > Hi Göran - > > Unbelievable. You basically just described the very idea that started this whole traits thing. When Nathanael worked with us at Disney, the traits direction came out of discussions where we wanted him to think precisely along the lines you're describing. My original thoughts on this matter was that I wanted to have something more like (biological) cells - entities that are made up of smaller things (objects), that have an inside and an outside (made up of other objects instead of abstractions like the interface/implementation distinction), that forward signals (messages) to the appropriate receptors etc. Ted and Ian at VPRI recently wrote short notes on this topic: http://www.vpri.org/pdf/m2009014_membrane.pdf (Ted) http://www.vpri.org/pdf/m2009007_COLA_kern.pdf (Ian) Of course, there's also Newspeak. > What you're describing is very much in line with my thoughts at that point which also included more actor-like structures (some of which was later realized in Tweak and once I found E got an even stronger version in Croquet). I was thinking today that it would be nice to merge Croquet's #future changes into the trunk's compiler. It doesn't seem inappropriate to me; does anyone disagree? For those unaware of what I'm talking about, Croquet's compiler lets you write code like: 'anObject future foo: theArgs', which is translated at the AST level to 'anObject futureSend: #foo at: deltaMSecs args: theArgs'. It's up to the receiver to implement that method sensibly. Originally, Croquet had two implementors, Object and TFarRef, with quite different semantics. The latter is the core mechanism by which Croquet distributes messages to object-replicas over the network. At Teleplace, we've since implemented it in several more classes (although most of these are instances of one pattern, and could be unified). A separate discussion would be whether there should be a default implementation in Object, and what it should look like. I think that it's fine to not have a default implementation until a strong proposal is made. Cheers, Josh > Cheers, > - Andreas > > Göran Krampe wrote: >> Hi all! >> Andreas Raab wrote: >>> BTW, I really don't think there is much difference between mixins and traits; all forms of MI are fairly equivalent. >>> >>> Cheers, >>> - Andreas >> As most of us I was also very positive when Traits first came to the scene. Now, I am a lot more jaded and think "well, sure, but are they worth it?". >> Given Andreas' statement above - wouldn't it be *much* cooler to evolve Smalltalk along the axis of composition instead of the axis of inheritance? >> I have always thought that having better mechanisms for delegation would be awesome, and would in most ways be much more powerful than inheritance (in whichever form). >> For example, what if one could declare that for class Foo (having ivars x, y, z) any message that would result in a DNU would instead be "delegated" to ivar x (and then y if no lookup is found in x either). >> This would be equivalent to tons of messages in Foo like: >> Foo>>name >> ^(x respondsTo: #name) ifTrue: [x name] ifFalse: [y name] >> (well, kinda, you get the picture - but also, taking care of x/y returning "self" in which case we probably should return the "Foo self" instead etc) >> In many ways the mechanism I am describing is "built into" languages like self and Slate (I think). >> IMHO the above is very useful and would allow fine grained composition similar to Traits but in a dynamic more object centric fashion. >> regards, Göran > > |
Josh Gargus wrote:
> Ted and Ian at VPRI recently wrote short notes on this topic: > http://www.vpri.org/pdf/m2009014_membrane.pdf (Ted) > http://www.vpri.org/pdf/m2009007_COLA_kern.pdf (Ian) Oh boy, you asked for it :-) Ted's paper reminds me a lot of some of the earliest discussions with Alan that came straight out of Macromedia Director - I always liked how you could just drag and drop behaviors onto an object and they would "just work" (incl. state and interactions with other behaviors). The other big motivation for me as was to deal with the problems that arise from the "fat class object" idea - i.e., the how to structure the (necessary) complexity for something as powerful as a general end-user scriptable, graphical, composable object without ending up like class Morph. Tweak was my first (and unfortunately last) shot at it. One thing that came straight out of the earlier discussion was that I realized that in order to hide complexity I needed to encapsulate it, i.e., create fully fledged self-contained entities (objects). In Tweak the result can be seen for example when looking at players and their (costume) aspects. They're kinda like the Morph equivalent but where Morph stuffs all of the "extra logic" straight into class Morph itself (look at the implementors of Morph>>color: or Morph>>borderWidth:) Tweak pushed this stuff out into the aspects so that the method looks like: CPlayer>>color: aColor "The color is owned by the fill aspect" ^fill color: aColor CPlayer>>borderWidth: aNumber "The border width is owned by the border aspect" ^border width: aNumber It worked by the aspects knowing the players they were owned by and interacting with them as needed (i.e., CFill>>color: would eventually call the player's invalidation method to cause a redraw). I still think that's a really great idea to structure complexity for several reasons. First of all, contrary to traits and other MI solutions you have *objects*, self-contained entities with encapsulated internal state and behavior. Secondly, by defining the interface between the parts you get a clearer separation of concerns between them, with clearly defined external and internal interfaces (i.e., try finding the methods relating to fill stuff in Morph, then go and try to find those that are part of the public interface). Third, this mechanism also gives you the "many versions of self" that the delegation discussions usually end up with by way of being able to decide whether to say "owner borderWidth: 2" or "self width: 2" from the aspect (the former being overridable by all subclasses of CPlayer; the latter defining this as a more private matter that can only be reimplemented by aspects of the same form). And of course such aspects can be composed the same way so it's turtles all the way down. Lastly, boilerplate like the above is trivial to automate and to fold into a class definition syntax. However, there is one major gotcha in this. It's that you need to design your entities differently. You can no longer assume that a class is completely self-contained (i.e., the class as an idea), but rather that it will have numerous collaborators that are equally important (which is more along the lines of the resulting object being a cell). This implies different design approaches and some of the rules of good OO design have limited applicability, for example the general rule of thumb that an owned object shouldn't know its owner, or the law of diameter. In a design like here, you would absolutely expect a Rectangle aspect to know the "owner" which gets notified about the change when you call the #left: method on it. To come full circle here, to me the problem with traits (really, any form of MI used for code composition) is that the end result is *always* something that looks like class Morph - complexity in a single centralized place, all messed up with renames, aliasing, exclusion. From my perspective there's no way around that problem as long as we're not starting to introduce internal structure. That would be objects. Cheers, - Andreas |
In reply to this post by Josh Gargus
On Wednesday 09 December 2009 07:38:31 am Josh Gargus wrote:
> n Dec 8, 2009, at 4:08 PM, Andreas Raab wrote: > > Hi Göran - > > > > Unbelievable. You basically just described the very idea that started > > this whole traits thing. When Nathanael worked with us at Disney, the > > traits direction came out of discussions where we wanted him to think > > precisely along the lines you're describing. My original thoughts on this > > matter was that I wanted to have something more like (biological) cells - > > entities that are made up of smaller things (objects), that have an > > inside and an outside (made up of other objects instead of abstractions > > like the interface/implementation distinction), that forward signals > > (messages) to the appropriate receptors etc. > > Ted and Ian at VPRI recently wrote short notes on this topic: > http://www.vpri.org/pdf/m2009014_membrane.pdf (Ted) objects can connect to a receptor, seek other objects, get an object instantiated (if necessary), communicate with them and disconnect. Objects retain their autonomy and gain the ability to collaborate with peer objects across an active container. Subbu |
Free forum by Nabble | Edit this page |