A Traits question

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

A Traits question

Sophie424
I have encountered several places where I would use traits if I could
parameterize the required methods (in addition to the provided methods,
which I can currently do).

TraitA>>a
  self x
TraitA>>b
  self y

Object subclass: MyClass
 "should distinguish required from provided, but simplified..."
  uses:  { TraitA @ { #a->#a1. #x->#x1 }.
           TraitA @ { #a->#a2. #x->#x2 } }


Where the intent is roughly equivalent to some intermediate traits:
TraitA>>a
  self x
TraitA>>b
  self y

Trait named: #TraitA1 uses: { TraitA }
TraitA1>>a1
  self x1

Trait named: #TraitA2 uses: { TraitA }
TraitA2>>a2
  self x2

Would something like this be malformed (in trait terms)? Was it considered
and rejected for some other reasons?

Thanks - Sophie




Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Sophie424
"itsme213" <[hidden email]> wrote

> Where the intent is roughly equivalent to some intermediate traits:
> TraitA>>a
>  self x
> TraitA>>b
>  self y
>
> Trait named: #TraitA1 uses: { TraitA }
> TraitA1>>a1
>  self x1
>
> Trait named: #TraitA2 uses: { TraitA }
> TraitA2>>a2
>  self x2

I left out...
Object subclass: #MyClass
  uses:  { TraitA1. TraitA2 }

- Sophie




Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Damien Cassou-3
I do not understand what you are trying to achieve.

On Thu, Feb 14, 2008 at 8:32 PM, itsme213 <[hidden email]> wrote:

> "itsme213" <[hidden email]> wrote
>
>
>  > Where the intent is roughly equivalent to some intermediate traits:
>  > TraitA>>a
>  >  self x
>  > TraitA>>b
>  >  self y
>  >
>  > Trait named: #TraitA1 uses: { TraitA }
>  > TraitA1>>a1
>  >  self x1
>  >
>  > Trait named: #TraitA2 uses: { TraitA }
>  > TraitA2>>a2
>  >  self x2
>
>  I left out...
>  Object subclass: #MyClass
>   uses:  { TraitA1. TraitA2 }
>
>  - Sophie
>
>
>
>
>



--
Damien Cassou

Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Sophie424
"Damien Cassou" <[hidden email]> wrote in message
>I do not understand what you are trying to achieve.

Say I have a trait which manipulates a collection inst-var:

Trait named #TCollectionInstVar
  >> add: x
            self iVar add: x
  >> remove: x
            self iVar remove: x
  >> do: aBlock
            self iVar do: aBlock

I have a class which could use this trait on two of its instance-variables:

Object subclass: Person
   instanceVariables: 'cars houses'
Person>>cars
   ^ cars ifNil: [cars := OrderedCollection new]
Person>>houses
   ^ houses ifNil: [houses := OrderedCollection new]

I want to use TCollectionInstVar on my Person class, twice, on #houses and
#cars. I need to rename the required methods to do this:
    TCollectionInstVar @ {
            #add -> #addCar.
            #remove -> #removeCar.
            #do -> #carsDo  }
        requiring: { #iVar -> #cars }
and
    TCollectionInstVar @ {
            #add -> #addHouse.
            #remove -> #removeHouse.
            #do -> #housesDo }
        requiring: { #iVar -> #houses }

This would be equivalent to using two (anonymous, auto-generated) traits
which are derived from TCollectionInstVar, which rename the required method
from #iVar to #cars and #houses, respectively (i.e. any send of "self iVar"
is rewritten to a "self cars" or "self houses").

Hope that example helps.

Thanks - Sophie




Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Sophie424
"itsme213" <[hidden email]> wrote
> Say I have a trait which manipulates a collection inst-var: ....

I had the #x->#y backwards in my last post. Below is the full example,
cleaned up and better named:

Say I have a trait which manipulates a collection inst-var:

Trait named #TCollectionInstVar
  >> addToInstVar: x
            self iVar add: x
  >> removeFromInstVar: x
            self iVar remove: x
  >> doOverInstVar: aBlock
            self iVar do: aBlock

I have a class which could use this trait on two of its instance-variables:

Object subclass: Person
   instanceVariableNames: 'cars houses'
Person>>cars
   ^ cars ifNil: [cars := OrderedCollection new]
Person>>houses
   ^ houses ifNil: [houses := OrderedCollection new]

I want to use TCollectionInstVar on my Person class, twice, on #cars and
#houses. I need to rename both provided & required methods to do this:
  TCollectionInstVar @ {
    #addCar: -> #addToInstVar: .
    #removeCar: -> #removeFromInstVar: .
    #doCars: -> #doOverInstVar: }
   requiring: { #iVar -> #cars } "change 'self iVar' to 'self cars'"
and
  TCollectionInstVar @ {
    #addHouse: -> #addToInstVar:
    #removeHouse: -> #removeFromInstVar.
    #doHouses: -> #doOverInstVar: }
   requiring: { #iVar -> #houses } "change 'self iVar' to 'self houses'"

This would be equivalent to using two (anonymous, auto-generated) traits
which are derived from TCollectionInstVar, which rename the required method
from #iVar to #cars and #houses, respectively (i.e. any send of "self iVar"
is rewritten to a "self cars" or "self houses").

Since many behaviors of an object are focused on just some part of its state
(wrapped by accessors), I find many places where I want to parameterize a
trait by the "required method" it will use to "call-back" to my class.
Having this "call-back" hard-coded in the trait with no way to override
makes that trait much less re-usable.

As Andreas pointed out, this would be solved with proper "renaming" of
methods (both provided and required, probably written #old->#new, not
#new->#old) when using traits, as opposed to the current "aliasing".

Hope I was clearer that time, and please tell me if I should be thinking
differently about this.

Thanks - Sophie




Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

stephane ducasse
Yes it is clearer. Now traits got aliasing but not renaming because it  
was simpler :)

Stef

On Feb 20, 2008, at 5:35 PM, itsme213 wrote:

> "itsme213" <[hidden email]> wrote
>> Say I have a trait which manipulates a collection inst-var: ....
>
> I had the #x->#y backwards in my last post. Below is the full example,
> cleaned up and better named:
>
> Say I have a trait which manipulates a collection inst-var:
>
> Trait named #TCollectionInstVar
>>> addToInstVar: x
>            self iVar add: x
>>> removeFromInstVar: x
>            self iVar remove: x
>>> doOverInstVar: aBlock
>            self iVar do: aBlock
>
> I have a class which could use this trait on two of its instance-
> variables:
>
> Object subclass: Person
>   instanceVariableNames: 'cars houses'
> Person>>cars
>   ^ cars ifNil: [cars := OrderedCollection new]
> Person>>houses
>   ^ houses ifNil: [houses := OrderedCollection new]
>
> I want to use TCollectionInstVar on my Person class, twice, on #cars  
> and
> #houses. I need to rename both provided & required methods to do this:
>  TCollectionInstVar @ {
>    #addCar: -> #addToInstVar: .
>    #removeCar: -> #removeFromInstVar: .
>    #doCars: -> #doOverInstVar: }
>   requiring: { #iVar -> #cars } "change 'self iVar' to 'self cars'"
> and
>  TCollectionInstVar @ {
>    #addHouse: -> #addToInstVar:
>    #removeHouse: -> #removeFromInstVar.
>    #doHouses: -> #doOverInstVar: }
>   requiring: { #iVar -> #houses } "change 'self iVar' to 'self  
> houses'"
>
> This would be equivalent to using two (anonymous, auto-generated)  
> traits
> which are derived from TCollectionInstVar, which rename the required  
> method
> from #iVar to #cars and #houses, respectively (i.e. any send of  
> "self iVar"
> is rewritten to a "self cars" or "self houses").
>
> Since many behaviors of an object are focused on just some part of  
> its state
> (wrapped by accessors), I find many places where I want to  
> parameterize a
> trait by the "required method" it will use to "call-back" to my class.
> Having this "call-back" hard-coded in the trait with no way to  
> override
> makes that trait much less re-usable.
>
> As Andreas pointed out, this would be solved with proper "renaming" of
> methods (both provided and required, probably written #old->#new, not
> #new->#old) when using traits, as opposed to the current "aliasing".
>
> Hope I was clearer that time, and please tell me if I should be  
> thinking
> differently about this.
>
> Thanks - Sophie
>
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Prof. Andrew P. Black

On 20 Feb 2008, at 11:47, stephane ducasse wrote:

Yes it is clearer. Now traits got aliasing but not renaming because it was simpler :)


Stef


Simpler was a large part of it.  We did consider renaming, and looked at the renaming facility in Eiffel.   "Simpler" does not just mean "simpler to implement"; the flattening property, which gives a nice simple semantics to traits, would go if we added renaming.  The claim that you can understand trait code without understanding traits would also go.

We made a judgement call that by not having renaming, and instead only "one sided" aliasing, the loss in utility would be outweighed by the gain in simplicity.  On the one hand, even if we were right then, we may no longer be right now: given that traits have succeeded, and that more people are now familiar with traits, the additional complexity may be easier to justify.  On the other hand, we may have been right then, and may still be right now.

There are alternative ways of solving the problem in itsme213's original post.  One would be to give traits private instance variables.  Then the trait TCollectionInstVar would be included twice, aliasing the exported methods in both cases, to addCar, addHouse, etc.  The name of the instVar itself would be private to the trait, and inaccessible from the class — this could be implemented by using a genSym for the insVar.   We still loose the flattening property, at least in its simplest form, but the idea of trait-private inst vars is simpler to understand, I think, than deep renaming, and is useful for a lot of applications.  Two applications that we thought of in 2002 were a trait that adds locking and a trait that adds transactions to an existing class.  The traits would need a place to put the lock or to put the Transaction id, and it seems much more elegant to make that place private to the trait.

Of course, it is also possible to solve itsme213's problem with traits as they stand, by making the name of the instVar a parameter to the trait methods, and using instVarNamed etc. to access it.   Or, just duplicating the code.  We have been duplicating code like this for 25+ years; it's part of the penalty that you pay for having such a tiny language as Smalltalk.

Andrew
 


Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Andreas.Raab
Andrew P. Black wrote:
> Simpler was a large part of it.  We did consider renaming, and looked at
> the renaming facility in Eiffel.   "Simpler" does not just mean "simpler
> to implement"; the flattening property, which gives a nice simple
> semantics to traits, would go if we added renaming.  The claim that you
> can understand trait code without understanding traits would also go.

How so? I'm not sure how renaming affects either the flattening property
or how one "can understand trait code without understanding traits" to
begin with ;-)

Cheers,
   - Andreas

Reply | Threaded
Open this post in threaded view
|

Re: A Traits question

Sophie424
In reply to this post by Prof. Andrew P. Black

"Andrew P. Black" <[hidden email]> wrote
>Of course, it is also possible to solve itsme213's problem with
>traits as they stand, by making the name of the instVar a parameter
>to the trait methods, and using instVarNamed etc. to access it.

If a class-implemented method called the trait-method this might be an
option. If the trait-method was the entry point, making the call to
class-implemented methods, I don't see how this could work.

Also not sure how (or how badly) flattening gets affected. I believe Eiffel
renaming exists alongside its multi-inheritance and class hierarchy
flattening.

- Sophie




Reply | Threaded
Open this post in threaded view
|

[squeak-dev] Re: A Traits question

Jason Johnson-5
In reply to this post by Prof. Andrew P. Black
On Wed, Feb 20, 2008 at 9:55 PM, Andrew P. Black <[hidden email]> wrote:

>
> There are alternative ways of solving the problem in itsme213's original
> post.  One would be to give traits private instance variables.  Then the
> trait TCollectionInstVar would be included twice, aliasing the exported
> methods in both cases, to addCar, addHouse, etc.  The name of the instVar
> itself would be private to the trait, and inaccessible from the class — this
> could be implemented by using a genSym for the insVar.   We still loose the
> flattening property, at least in its simplest form, but the idea of
> trait-private inst vars is simpler to understand, I think, than deep
> renaming, and is useful for a lot of applications.  Two applications that we
> thought of in 2002 were a trait that adds locking and a trait that adds
> transactions to an existing class.  The traits would need a place to put the
> lock or to put the Transaction id, and it seems much more elegant to make
> that place private to the trait.

Well, since the above is just an example of true multiple inheritance
it might be worth while to look at how Lisp deals with it in CLOS.