Traits approaching mainstream Squeak

Andreas Raab andreas.raab at gmx.de
Sun Aug 28 22:09:47 UTC 2005


Daniel Vainsencher wrote:
> Well, while Class and friends may be an exception, I would generally shy 
> from creating a class with 15 responsibilities. Too much responsibility 
> leads to madness. Maybe it needs to learn to delegate and cooperate more 
> :-)

I actually agree - but you know if you take that approach seriously you 
shouldn't use traits at all ;-) Simply because you have given people a 
tool for dealing with more complexity per object and they will use it.

>> That would be different from saying traits generally cover interfaces 
>> (say, "Collection") which typically span multiple responsibilities.
> 
> I don't know that responsibilities are finer grained than interfaces. 
> What do you mean by an interface?

"Something larger". Ho hum. Yeah, not really good. But I think you'd 
probably agree that adding, removing, enumerating, searching, 
arithmetic, vector math aren't just one responsibility. But Collection 
is one interface in my understanding.

[Interestingly and somewhat OT, I decided early on that for Tweak I 
would include the adding/removing responsibilities of collections for 
all players and the full collection interface only for holders. I think 
there is really need for both ways of looking at it]

>> and (I think) will often lead to a unique possible combination of 
>> traits to create a functioning entity. If that were correct, I would 
>> claim that having a more coarse-grained representation would be 
>> advantageous (see below).
> 
> In this case, I would agree with you. One particular example of this 
> situation would be mutually recursive functions separated into two 
> traits. Though even there, one might one to have an alternative 
> implementation of one of them.
> 
> However, I'm not sure why you assume that unique feasible combinations 
> would be common.

Of course, I can't say at this point. But it felt that way when I looked 
over the example I chose (ClassDescription) since there were plenty of 
what I considered "obscure" requirements which could only be satisfied 
by specific traits etc. So it felt that if you pull one string you get 
an entire spider web falling on you.

>> Well, that's the 64k question, isn't it? If all you have is a hammer 
>> everything looks like a nail, but if you have multiple tools you need 
>> to find out what to use when and where and how. Like, for example, why 
>> not implement traits purely using traits? We could (trivially) get rid 
>> of the abstract superclasses PureBehavior, TraitBehavior, and 
>> TraitDescription, and just have a single Object subclass: #Trait with 
>> all the behavior mixed in via traits. What would we gain? What loose?
> 
> I think one general gain from Traits is that the question "how do 
> separate the responsibilities of this class" becomes orthogonal to 
> "which of those subsets of responsibility am I more likely to reuse in 
> the future", which would be one reason to make Behavior a superclass to 
> ClassDescription, rather than the opposite.
> 
> I don't yet see a loss. Is there any loss at all for this case? for 
> class and its superclasses?

That's a great question.

>> For TAccessingMethodDictDescription (which I am choosing only because 
>> it's the first in the category and not too long) we have the following 
> 
> 
>  >   #compiledMethodAt:ifAbsent:
>  >     -> TMethodDictionaryBehavior
> This is a natural requirement

But making precisely my point. Why separate "accessing a method 
dictionary" from the "being a method dictionary"? Can you ever "access a 
method dictionary that is not a method dictionary"? It just feels as if 
we're putting needless complexities on top of each other.

>> requirements:
>>   #instanceSide
>>     -> ClassDescription/TraitDescription
>>   #isMeta
>>     -> TTestingDescription
> 
> These are only needed because of #addSelectorSilently:withMethod:, and 
> its use of the peculiar interface of #noteAddedSelector:meta:. Of the 
> two non trivial implementations of the latter, only one uses the isMeta 
> information, so why should it be part of the interface? this interface 
> by itself mixes two responsibilities. Just omit the isMeta parameter, 
> send the message to self, instead of self instanceSide, and have what 
> clients need the information, and wish to assume a dual hierarchy, ask 
> #isMeta. So one thing we learn is that fine grained Traits can be done 
> well only if the interfaces and implementation are designed to keep 
> responsibilities separate. I'm not arguing that that's necessarily 
> something we should always do BTW, but its worth considering.

I agree with your analysis - it's actually a nice example for how you'd 
(re-)design this with traits.

>>   #updateOrganizationSelector:oldCategory:newCategory:
>>     -> TTraitsCategorisingDescription
>>   #whichCategoryIncludesSelector:
>>     -> TBasicCategorisingDescription
> 
> This is interesting. These are required in #removeSelector:, and only 
> because of the existence of categorization. If I find myself wanting to 
> use TMethodDictAccessing without method categories, I could certainly do 
> so, by overriding this single method in the class/trait using it.

Agreed, too.

> I think "people trying to understand the system" is too wide. People 
> trying to understand behavior should probably not concern themselves 
> with traits at all. Just look at the resulting flat class. For people 
> trying to understand the dependencies (maybe they want to reuse), the 
> complex dependencies were already there before the traits. The method 
> categories we about as fine grained, except they did help because they 
> have no semantics and no tool support (requires).

But here I disagree. I think you will *want* to show people the traits 
in a sensible way when they explore a class - if only to make it easier 
to see what responsibilities are involved, what the class uses verbatim 
and where it differs (extremely important as this is where the 
"integration" happens and as we all know integration is one of the 
hardest things to do well and to understand).

> So I'd claim that a good composition into Traits, with only slightly 
> less granularity than there is there now, would be an improvement for 
> understandability on having only the flat view.

If not, what would be the point, eh? ;-)

>> Understanding or changing any of these seems to become a major effort 
>> in navigating through the various traits and for some reason that 
>> disturbs me greatly. There is a basic lack of locality of reference - 
>> something that has always disturbed me in Smalltalk but with traits 
>> (or rather: the way it's being used right now in the class hierarchy) 
>> this may be going to extremes.
> 
> Actually, trying to track down the information I needed for the analysis 
> above, I feel exactly what you mean. To support understanding the link, 
> we really need tool support to tell us "what methods are requiring 
> this", and "who is providing method in this context".

A simple yet important thing to do would be to hide the "implementors" 
if they use ("inherit") the method from a trait verbatim. This is 
equivalent to not showing subclasses which albeit responding to a 
message do not implement it "locally".

>> It may also be worthwhile to get away from the goal of "maximizing 
>> code reuse" and rather "maximize interface consistency". To
>> me the latter is more important that the former - I don't mind a bit 
>> of duplicated code if (for example) it makes it easier to understand 
>> the system or to reduce dependencies. 
> 
> I disagree with that - code duplication causes the creation of 
> inconsistent variations, and that causes bug-fix hell, and destroys 
> coherence.

That really depends. Code duplication does all that but it also 
decouples subsystems (good) and makes sure one has control over critical 
portions of code (good) and then some ;-) It all depends on how much and 
where and why code is being duplicated. In general, I agree that within 
a (sub-) system under your control code duplication is a bad idea but 
if, for example, a critical algorithm depends on the (current) rounding 
behavior of line segment intersection it may be very well worthwhile to 
duplicate it since somebody might go and change that rounding behavior 
without you knowing it.

The reason why I am mentioning this is that with traits you can have 
many of these "remote effects" since it may not obvious to you which 
other users of that trait there are. This is a hard problem and one 
where I think that at times people may be better served duplicating code.

>> But one of the most powerful features of traits is that we can extend 
>> an interface for all classes implementing it and can do so in a 
>> concrete way - so that a new method like Collection>>gather: can be 
>> implemented in the Collection trait (interface) and will automatically 
>> apply to all of the users (implementors) of that trait.
> 
> Yes, that sounds like a good usage scenario. This is orthogonal to the 
> granularity though. I could certainly have an "interface" level Trait, 
> itself constructed from "responsibility" level Traits, and then widening 
> a responsibility also widens the implemented interface. And the 
> interface would be understandable as a flat thing, if you don't care 
> about the division of responsibilities.

And that may actually be a very good way of dealing with it (see the 
Tweak reference above).

> Yup. Time to experiment. I think it will take about 3 weeks before we 
> can do the preparatory work for inserting of Traits into 3.9a. Any 
> bugs/lacks that you all help us find and fix in the demo image till that 
> time can avoid being seen by all alpha, so you're encouraged to do so.

I won't be able to do much - I'll be traveling (sigh) again for the next 
two weeks and not be able to do any work in this area or even 
participate in the discussion much (if at all).

Cheers,
   - Andreas



More information about the Squeak-dev mailing list