Traits approaching mainstream Squeak

Daniel Vainsencher daniel.vainsencher at gmail.com
Sun Aug 28 21:01:59 UTC 2005



Andreas Raab wrote:
> I think this can be interpreted as an argument for generally "fine 
> granularity traits" since it seems that most classes will have multiple 
> responsibilities and thus traits would almost always cover only a single 
> responsibility for any class (15 responsibilities for ClassDescription 
> sounds about right to me). 
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 :-)

This, BTW, seems to be a genuine difference of style among Smalltalkers 
- some believe that a class should have 1-3 responsibilities, and let 
its collaborators do other things without it. It seems that 
ClassDescription, while collaborating with the compiler, its methods 
categorization, the class categorization, and the filein out code, 
treats them as its own responsibilities, with significant amounts of 
code in CD devoted to them. I for one would prefer that a class simply 
not know where its code comes from. At most it might have a couple of 
class extensions delegating to the compiler, but they'd be almost empty 
of code.

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

> I am not sure I agree here btw. 
I'm not sure about this opinion myself. And I would not, at this point, 
create traits for responsibilities unless I need to share them. In a 
specific context that requires the sharing, I might find myself acting 
differently.

> Although you are right in such that a 
> small trait will typically have a small set of requirements these 
> requirements can spread very easily 
I don't understand what "spread very easily" means here. I'm also not 
sure how it relates to the rest of your argument.

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

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

>> I for example think that the current inheritance of TraitBehavior and 
>> Behavior from PureBehavior is inappropriate, and should by replaced 
>> sharing the methods using a Trait usage. The reason for PureBehavior 
>> existing in the demo is historical - before Traits started 
>> functioning, that way of sharing implementation wasn't available. That 
>> change is going to happen before integration with mainstream, among 
>> things because it would make the Kernel hierarchy more similar to what 
>> is there now, therefore minimization integration costs.
> 
> I see. It would be interesting for me to see an actual design from first 
> principles that ignores the restrictions we have right now (like VM 
> dependencies) and just tries to build a comprehensive set of 
> abstractions.
Jecel, maybe you can describe the relevant parts of NeoSmalltalk?

>> I'm not familiar with all of it. Lets discuss something specific, so 
>> we can learn from it. Lets look at some specific Trait that annoys you 
>> and see how it can be improved.
> Well, to come back to the first part of the message let's look at the 
> requirements for traits in ClassDescription and where we find them:
> 
> 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

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

The rest is required by the existence of organizations.
>   #organization
>     -> ClassDescription/TraitDescription
This is a requirement for a variable, doesn't require a new Trait.

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

Anyway, I'm not sure what the justification is for having two traits 
that deal with categorization (TBasic.. and TTraits..).

> In this case support for a tiny trait is typically implemented using 4-5 
> further traits 
Actually only 3, count em :-), and maybe they should be two.

>(which in turn will use other traits which makes up for 
> the full set of traits used in ClassDescription) and I wonder what this 
> does to people trying to understand the system. 
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).

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.

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

> Do I know how to improve this? Well, I don't exactly, other than saying 
> perhaps we should look at having few "macro traits" representing 
> multiple responsibilities instead of "micro traits" for each individual 
> responsibility. 
This is definitely another option worth considering. I would not be 
surprised if taste and circumstance would drive both "opposed 
approaches" to quite similar results in practice. At least while "no 
duplication" is considered a requirement.

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

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


[Showing requires]
> Not really. Virtual categories might work. Coloring might. Even just 
> annotating the browser so it lists the trait in the annotation pane 
> might work.
Oh, I forgot, that too was in the prototype... :-)
Well, we'll see.

>>  > In general, I think it would be helpful if some guidelines could be
>>  > established for how to use traits in practice.
>> I think the best we can do at the moment is -
>> A. Study and discuss the existing design examples - the collections 
>> restructure (I don't know if the code is available somewhere) and the 
>> kernel restructure as seen in the demo.
> I haven't seen the collection code but I'd be interested in it.
in http://www.iam.unibe.ch/~scg/Research/Traits/
papers 1 and 6 have relevant things to say, though they don't give the 
code. I don't know if that's available.

>> B. Start playing with these new options in our own work.
>> It's new, like Mike said, the patterns book for Traits has not yet 
>> been written...
> Well, I think we have a few good things that we should try, including: 
> What if we avoid inheritance completely? (I think that's worth a try 
> just to see how it feels) How would a coarse-grain set of traits "feel"?
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.

Daniel



More information about the Squeak-dev mailing list