[Pharo-project] Smells looking at collections

Levente Uzonyi leves at elte.hu
Sun Oct 24 20:17:29 EDT 2010


On Sun, 24 Oct 2010, Stéphane Ducasse wrote:

>>>
>>>>
>>>>> 2) well, this is difficult to get the money for the butter and the butter - we are trying.
>>>>
>>>> If you can compose classes the way you can add a trait to a class now with class and optional method level instance+class variable mapping, then you're done. It would be a lot simpler to use it _and_ it would also be a lot easier to implement it. Especially the tools part.
>>>
>>> I'm interested to hear more about that.
>
> so can you explain what you meant because I did not understand it.

Okay, here is an example of my class composition idea. It works like 
Traits, but it supports state and there's no distinction between a trait 
and a class. Let's say I have a class named BidirectionalLink which can be 
used as a link in a linked list (with a head element). This will be used 
as a stateful trait. It's definition is like this:

Object subclass: #BidirectionalLink
 	instanceVariableNames: 'next previous'
 	classVariableNames: ''
 	poolDictionaries: ''
 	category: 'ClassCompositionExample'

It has a few methods:

BidirectionalLink >> next
 	^next

BidirectionalLink >> next: aBidirectionalLink
 	next := aBidirectionalLink

BidirectionalLink >> previous
 	^previous

BidirectionalLink >> previous: aBidirectionalLink
 	previous := aBidirectionalLink

BidirectionalLink >> unlink
 	previous next: next.
 	next previous: previous

BidirectionalLink >> linkAfter: aBidirectionalLink
 	previous := aBidirectionalLink.
 	next := aBidirectionalLink next.
 	aBidirectionalLink next: self.
 	next previous: self

I have an existing class, let's call it ODatedEntry :). It has the 
following definition:

ECSelectorEntry subclass: #ODatedEntry
 	instanceVariableNames: 'date'
 	classVariableNames: ''
 	poolDictionaries: ''
 	category: 'Ocompletion'

As you can see, it inherits some state and behavior from ECSelectorEntry. 
My goal is to use the instances of this class in a linked list. So these 
objects should implement the same protocol with the same behavior as 
BidirectionalLink. I can't make it a subclass of BidirectionalLink, 
because I also need the behavior and state from ECSelectorEntry and we 
don't have multiple inheritance. So I'll compose the two classes. First I 
add the necessary instance variables to the class. Let's call them 
nextEntry and previousEntry:

ECSelectorEntry subclass: #ODatedEntry
         instanceVariableNames: 'date nextEntry previousEntry'
         classVariableNames: ''
         poolDictionaries: ''
         category: 'Ocompletion'

Then do the composition:

ECSelectorEntry subclass: #ODatedEntry
 	uses: BidirectionalLink
         instanceVariableNames: 'date nextEntry previousEntry'
         classVariableNames: ''
         poolDictionaries: ''
         category: 'Ocompletion'

Now this doesn't work, because ODatedEntry doesn't have instance variables 
named next and previous, but the methods of BidirectionalLink would like 
to use them. Of course I could have used those names in the previous step 
and (with a working implementation) this would just work out of the box. But 
the example is about the instance variable mapping. Let's say ~ is the 
composition operator which defines variable mapping for a class or a 
method. Then I can write the following:

ECSelectorEntry subclass: #ODatedEntry
         uses: BidirectionalLink ~ { #nextEntry -> #next. #previousEntry -> #previous }
         instanceVariableNames: 'date nextEntry previousEntry'
         classVariableNames: ''
         poolDictionaries: ''
         category: 'Ocompletion'

This means: take all methods from BidirectionalLink, but replace the 
variable named next with nextEntry and previous with previousEntry.
So now ODatedEntry understands #next, #next:, #previous, #unlink, etc.

This is the basic concept. There are some open questions, like:
- what happens when a composed method (or a method sent by a composed 
method, etc) has a super send?
- will the class also get the methods of the superclasses of the "trait"?
- how does it work on the class side?
- what about class variables?
- what if I don't want to use all methods, just a few?

>
>
>>> Tell us more. The problem we faced was
>>> 	- offset access = you cannot reuse bytecode of a trait because the order of the offset can be different in each trait users
>>
>> If you mean that a CompiledMethod of a trait cannot be added to the class' method dictionary, than that's not an issue. The current Trait implementation was changed, because shared CompiledMethods caused other problems.
>
> no this is not what I meant
>
>> If you mean that the same bytecodes can't be used, than that's neither a problem, because you can and should be compile the method again. Sharing trailer bytes may cause problems.
>> So adding a method from a trait to a class is simply recompiling it in the class' context.
>
> this is what we wanted to avoid.
> Also because you may have to recompile all the other methods of the class hierarchy because if a trait add an instance
> variable then you should recompile the subclasses when a trait get added with a state in the superclass.

My idea is that traits don't add instance variables. The user of the 
trait maps the trait's variables to their own by name. So if a trait gets
a new variable, then only the trait and subclasses have to be recompiled. 
The recompilation is postponed until a method of the trait which uses the 
new instance variable is added to a class.

>
>> Instance variables should be used by name during compilation. If there's a name collision then use the instance variable map I mentioned above.
>
> what is that the instance variable map?
> take the time to write an example

See above.


Levente

>
>>> 	- initialization of instances variables at the trait level and the composition at the class levele
>>
>> You can always rename a trait's method in your class. So if the trait has an #initialize method, then simply rename it to #initializeFooBar and send it from the class' #initialize method.
>
> Yes this is what the javascript implementation does but this is not that nice but may be there is no better solution.
>
> So indeed we could think about adding state.
>
>
>
>>
>>
>> Levente
>>
>>>
>>>>
>>>>> 3) again if nobody does anything and we just all cry on ourselves then nothing will happen.
>>>>
>>>> Tools are a must. No tools - no users.
>>>
>>> Exact.
>>>
>>>>> So for now identifying traits and learning is the way. Then we can refactor, redesign
>>>>
>>>> Well, Traits are in Squeak since 2006, IIRC they were available a few years earlier. So in the last X (at least 4) years the only good candidate to become a Trait was Magnitude.
>>>
>>> Come on.
>>> I will not answer to such statement because I'm positive thinking.
>>>
>>> Stef
>>> _______________________________________________
>>> Pharo-project mailing list
>>> Pharo-project at lists.gforge.inria.fr
>>> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>> _______________________________________________
>> Pharo-project mailing list
>> Pharo-project at lists.gforge.inria.fr
>> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>
>
> _______________________________________________
> Pharo-project mailing list
> Pharo-project at lists.gforge.inria.fr
> http://lists.gforge.inria.fr/cgi-bin/mailman/listinfo/pharo-project
>


More information about the Pharo-dev mailing list