Traits approaching mainstream Squeak

Andreas Raab andreas.raab at gmx.de
Sun Aug 28 01:48:53 UTC 2005


Hi Daniel -

>> For example, I think ClassDescription uses roughly 15 traits, many of 
>> which seem only to be used by ClassDescription/TraitDescription so 
>> there seems comparadly little reason to (for example) define 
>> TCopyingDescription, TCompilingDescription, TPrintingDescription etc. 
>> separately. Looking over the methods involved it almost feels as if 
>> the decision for defining the traits has been done along the lines of 
>> protocols / categories. Is this correct? 
> 
> Adrian or maybe Stef are better suited to respond to that as a specific 
> question. The general question is "What is the proper granularity of 
> Traits?" My opinions: methods in a trait should implement a specific 
> responsibility in a reusable way. To serve this, they should have a 
> small requirements footprint, such that any class/trait that wants an 
> implementation for this responsibility has to provide them anyway.

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). That would be different from saying traits 
generally cover interfaces (say, "Collection") which typically span 
multiple responsibilities.

I am not sure I agree here btw. Although you are right in such that a 
small trait will typically have a small set of requirements these 
requirements can spread very easily 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).

> Here's another general question -
> In my opinion at the moment, and I'm curious what other people think, in 
> the presence of Traits, there is no excuse for separating Behavior, 
> ClassDescription, and Class into a hierarchy. The responsibilities each 
> represents could be reused separately if they were Traits. So the only 
> clearly compelling reasons to create classes become that you want to 
> have instances with that shape and behavior. An abstract class doesn't 
> seem to make too much sense. So when should we use Traits to replace 
> inheritance as a mechanism for sharing implementation? always? Never 
> except when needed to avoid duplication or the complexities of delegation?

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?

>> In which places have you been avoiding inappropriate inheritance?
> 
> 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. Anyone up for it? It doesn't need to be implemented just 
being able to talk about where the responsibilities lie, what to reuse 
and how would be interesting.

>> It seems to me that a valid alternative for avoiding inheritance 
>> problems is instead of duplicating the Behavior/ClassDescription/Class 
>> hierarchy via TraitBehavior/TraitDescription/ClassTrait would be to 
>> make  Trait delegate to an appropriate class description (e.g., a 
>> trait uses a class description for managing the Behavior side of 
>> things). Can you say anything about how such a design would have 
>> affected the definition of traits for these kernel classes themself?
> 
> IIUC this proposal, it would require creating a class description for 
> every Trait. But a ClassDescription inheriting from Behavior know too 
> much and does too much - it is a full descriptor for object with 
> behavior, which a Trait isn't and doesn't need.

Yeah, I take this back. Delegation to an existing ClassDescription is 
probably not the right way to do it.

>> BTW, the reason I'm asking these questions is to get a better 
>> understanding on how one uses traits effectively. Looking over the 
>> current uses it struck me that using a large number of fine-grained 
>> taits can make it actually harder to understand the system. At least 
>> that was my feeling when I looked over the current structure - none of 
>> the traits are fully defined internally 
> 
> 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 
requirements:
   #compiledMethodAt:ifAbsent:
     -> TMethodDictionaryBehavior
   #instanceSide
     -> ClassDescription/TraitDescription
   #organization
     -> ClassDescription/TraitDescription
   #isMeta
     -> TTestingDescription
   #updateOrganizationSelector:oldCategory:newCategory:
     -> TTraitsCategorisingDescription
   #whichCategoryIncludesSelector:
     -> TBasicCategorisingDescription

In this case support for a tiny trait is typically implemented using 4-5 
further traits (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. 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.

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

> About the required browser -
> 1. Did you use the version I announced? an earlier beta had a critical 
> bug, though it was in the other direction - sometimes showed too few 
> methods.

I'm not sure. I used the image that you referred to and I checked if 
there was a newer version on squeaksource (there wasn't) so I think the 
version is okay.

> 2. Do note that requirements are a non trivial property to compute 
> (including in our heads). One reason is that requirements are often 
> inherited. For example, Object implements #stepIn: which self sends 
> #step. Therefore, everything in the system that doesn't implement #step 
> and does inherits #stepIn: requires #step.

Oh, I see. That was it - I was wondering why, for example, SmallInteger 
would require #step.

>> I think it is critical that one be able to see which methods come from 
>> traits and which one's don't. For example, I was curious if Class had 
>> any of its own methods or if all of the methods come from the three 
>> traits it uses[*] and also which traits are used by which classes 
>> (e.g., "all users of TVariablesClass").
> 
> Right. In the prototype this happened in two ways: non-locally defined 
> methods were green, and under each class that has traits, in addition to 
> those, there was a computed entry "-own-" that shows only local methods.
> 
> Have a preference?

Not really. Virtual categories might work. Coloring might. Even just 
annotating the browser so it lists the trait in the annotation pane 
might work.

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

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

Cheers,
   - Andreas



More information about the Squeak-dev mailing list