On traits composition

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
42 messages Options
123
Reply | Threaded
Open this post in threaded view
|

On traits composition

Andreas.Raab
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


Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Tapple Gao
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)

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Andreas.Raab
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

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Igor Stasenko
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 :-)
>
+1. It should be more straightforward.
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.

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Andreas.Raab
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




Reply | Threaded
Open this post in threaded view
|

Re: Re: On traits composition

Nicolas Cellier
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

Reply | Threaded
Open this post in threaded view
|

Re: Re: On traits composition

Igor Stasenko
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.

Reply | Threaded
Open this post in threaded view
|

Re: Re: On traits composition

Nicolas Cellier
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.
>
>

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Colin Putney
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

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Igor Stasenko
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.

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Colin Putney

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

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Igor Stasenko
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.

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Eliot Miranda-2


On Tue, Dec 8, 2009 at 11:49 AM, Igor Stasenko <[hidden email]> wrote:
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.

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.



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.




Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Andreas.Raab
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

Reply | Threaded
Open this post in threaded view
|

Re: On traits composition

Andreas.Raab
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

Reply | Threaded
Open this post in threaded view
|

How about... something completely different? (Re: Re: On traits composition)

Göran Krampe
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


Reply | Threaded
Open this post in threaded view
|

Re: How about... something completely different? (Re: Re: On traits composition)

Andreas.Raab
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
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Re: How about... something completely different? (Re: Re: On traits composition)

Josh Gargus

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
>
>


Reply | Threaded
Open this post in threaded view
|

Re: How about... something completely different? (Re: Re: On traits composition)

Andreas.Raab
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

Reply | Threaded
Open this post in threaded view
|

Re: Re: How about... something completely different? (Re: Re: On traits composition)

K. K. Subramaniam
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)
How is this different from a "Bus" (e.g. D-Bus) -  an active container where
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

123