Harmony classes [Was: Operator overloading revisited]

Mark S. Miller erights at google.com
Sat Jul 25 19:42:46 PDT 2009

On Fri, Jul 24, 2009 at 1:08 AM, Tom Van Cutsem<tvcutsem at vub.ac.be> wrote:
>>> On 2009-07-22, at 10:52EDT, Mark S. Miller wrote:
>> 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?

This change of semantics was more exploratory than purposeful. A
retroactive rationalization though is that a conflict that isn't used
shouldn't need resolution. C++ does something like this for its
multiple inheritance. Of course, they detect possible use statically
as well, so they don't pay the price of delaying the failure till the
use actually occurs.

If desired, it would be simple to alter the scheme I posted so that it
signals the conflict at composition time instead. I have no strong
opinion on which we should prefer.

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

Yes. That's the purpose of the 'self' parameter in that code.

> 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' prope[r]ty 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?

Yes, except for one detail. In this case, it is newDesc's 'value'
property rather than its get property. Starting with ES5, a property
is either a "data property" or an "accessor property". The descriptor
of a data property has the form
    { value: <any>, writable: <boolean>, enumerable: <boolean>,
configurable: <boolean> }
The descriptor of an accessor property has the form
    { get: <function () -> any>, set: <function (any)>, enumerable:
<boolean>, configurable: <boolean> }

Reading a data property is equivalent to reading the value of the
'value' property of a descriptor of the data property's current state.
Reading an accessor property is equivalent to calling the value of the
'get' property of a descriptor of the accessor property's current
state. The start method in the example is a data property, so its
descriptor would be of the first form.

I used an accessor property in my original post only to represent a
conflicted property, so the complaint would happen on any attempt to
read the property, whether or not the read was followed by a call.

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

Yes, exactly. In this pattern, as in your paper, lexical variables are
not affected by trait composition. For a property 'foo' exported by a
trait, within that trait definition it can be referred to either by
lexical variable 'foo' or as 'self.foo'. In the first case, it refers
to the 'foo' locally defined by that trait itself. In the second, it
refers to whatever 'foo' is finally bound to on the self object
resulting from the composition of traits.

>> ------------------------
>> 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);
>> }
>> ------------------------


More information about the es-discuss mailing list