Harmony classes [Was: Operator overloading revisited]

Tom Van Cutsem tvcutsem at vub.ac.be
Fri Jul 24 01:08:35 PDT 2009


> On Wed, Jul 22, 2009 at 11:30 AM, P T Withington<ptw at pobox.com> wrote:
>> On 2009-07-22, at 10:52EDT, Mark S. Miller wrote:
>>
>>> Lately Alex and I been
>>> reading <http://prog.vub.ac.be/Publications/2009/vub-prog-tr-09-04.pdf 
>>> >.
>>> Later today I will post some initial thoughts on how this could also
>>> be supported in within the same general framework.
>>
>> Thanks for this pointer.  I am just starting to read and it looks
>> intriguing.  Traits that also have state might just make me give up  
>> my
>> mixins!

I'm very pleased to see that our work is relevant for the Javascript  
community. If it's any help, the slides I used to present the paper a  
couple of weeks ago at ECOOP09 are available at: http://prog.vub.ac.be/~tvcutsem/presentations/Traits-ECOOP09.pdf

Some comments on Mark's proposal follow:

> Given the following helper abstractions:
>
> ------------------------
> const traitConflict() {
>  throw new Error("trait conflict");
> }
>
> const conflictDesc = Object.freeze({
>  get: traitConflict,
>  set: undefined,
>  enumerable: false,
>  configurable: true
> });

Do I get it right that a trait composition conflict is only raised  
upon accessing the conflicting property? In AmbientTalk, the error is  
signaled at composition-time (i.e. as if your addTrait method would  
raise an exception). What's your rationale for deferring the exception  
until the property is accessed?

> const isSameDesc(desc1, desc2) { ... }
>
> const addTrait(self, trait, opt_advice) {
>  const advice = opt_advice || {};
>  Object.getOwnPropertyNames(trait).forEach(const(k) {
>    const newDesc = Object.getOwnPropertyDescriptor(trait, k);
>    if (k in advice) {
>      const k2 = advice[k];
>      if (k2) { k = k2; /*String(k2); ? */ } else { return; }
>    }
>    const oldDesc = Object.getOwnPropertyDescriptor(self, k);
>    if (oldDesc) {
>      if (isSameDesc(oldDesc, newDesc)) {
> 	// already cool
>      } else {
> 	Object.defineProperty(self, k, conflictDesc);
>      }
>    } else {
>      Object.defineProperty(self, k, newDesc);
>    }
>  });
> }
> ------------------------

If I understand correctly, the line

	Object.defineProperty(self, k, newDesc);

installs the trait's 'newDesc' property in the object denoted by  
'self'. Two issues need be taken into account if the property refers  
to a method:

- the trait method needs a way to refer to the composite, such that it  
can invoke required methods. From your example below I infer that  
'self' can be made to refer to the composite because you explicitly  
parameterize classes with 'self'. This makes sense, especially for  
traits which can be seen as 'incomplete classes'.

- if the trait method refers to lexically free identifiers, will these  
identifiers be bound correctly if the method is invoked on the  
composite object? More concretely, using the example below: if the  
method 'start' is invoked on a ParticleMorph, can I rest assured that  
the reference to 'timer' in AnimationTrait's 'start' method refers to  
AnimationTrait's timer?

I think it works because you represent methods simply as functions  
(closures). I assume that newDesc's 'get' propety refers to a function  
that represents the trait method, and this function has closed over  
its lexical scope and will correctly refer to lexically free  
identifiers even when installed in a different object. Am I right? If  
this is the case, that's great because it avoids the need for Self- 
style delegation, which we required in our paper because methods were  
not mere functions (they were implicitly parameterized with 'self',  
and delegation was required to late-bind 'self' to the composite).

> and using no sugar beyond that defined in the thread starting at
> <https://mail.mozilla.org/pipermail/es-discuss/2009-March/ 
> 009115.html>,
> here is the main example from Tom's paper:
>
> ------------------------
> class AnimationTrait(self, refreshRate) {
>  const timer = makeTimer();
>  public start() {
>    timer.everyDo(refreshRate, const() { self.animate(); });
>  }
>  public stop() { timer.reset(); }
> }
>
> const ParticleTrait(self, radius, moveRate, dx, dy) {
>  const resultTrait = {};
>  addTrait(resultTrait, CircleTrait(self, radius));
>  addTrait(resultTrait, AnimationTrait(self, moveRate), {
>    start: 'startMoving',
>    stop: false
>  });
>  addTrait(resultTrait, object {
>    public animate() { self.move(dx, dy); }
>  });
>  return Object.freeze(resultTrait);
> }
> const ParticleMorph(radius, moveRate, dx, dy) {
>  var self = {};
>  addTrait(self, ParticleTrait(self, radius, moveRate, dx, dy));
>  return Object.freeze(self);
> }
> ------------------------
>
> Once we know whether or not we want to adopt traits, and whether we
> wish to adopt precisely Tom's traits semantics, we could consider
> further sugar for supporting exactly this pattern. Until then, and in
> order to get to such a state of clarity, it is pleasing that traits
> semantics such as Tom's can be expressed well directly in terms of the
> currently proposed non-inheriting classes-as-sugar system.
>
>
> -- 
>    Cheers,
>    --MarkM



More information about the es-discuss mailing list