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