Traits library
Tom Van Cutsem
tomvc at google.com
Wed Feb 17 15:49:15 PST 2010
Hi,
1) I understand the motivation for traits (removing some of the magic of
> mixins), but I don't understand why that needs to be anything more than a
> lint-like tool, rather than insinuating itself into the implementation.
Good point. For stateless traits, I think you're right that a preprocessor
would be sufficient. For stateful traits, since state is captured using
lexical scoping, you end up with the same optimization issues involved with
getting the "objects-as-closures" pattern implemented efficiently. The same
goes for traits instantiated using "Trait.create" (which tries to uphold the
integrity of |this| by turning all of the trait's methods into bound
methods. Since the binding must be done on a per-instance basis, object
instances created in this way cannot share method implementations without
help from the implementation.
> The fact that you provide the order-sensitive `override` compositor weakens
> the case for strict traits. If override combination somehow made the
> overridden property accessible with a standard name (`super`), it seems to
> me you would have mixins. What am I missing?
>
You are absolutely right in stating that the `override` operator weakens the
strength of the traits approach. If you perform all of your trait
composition via `override`, you are effectively performing inheritance/mixin
composition, with the associated gotchas. The benefit of traits is of course
that they provide this other operator, `compose`, with nicer properties. The
intent of the traits library is of course that `compose` be used more
frequently than `override`. I provided `override` for two reasons:
1. the original traits model also includes it. In the original traits model,
when a composite trait is eventually composed with a class, the class and
the trait are composed using `override`.
2. as a convenience. `override` may sometimes simple be the right operator
for the job. If it captures the semantics of the composition you're trying
to express, there's nothing wrong with using it. The alternative would be to
use compose + resolve all conflicts by explicitly excluding all
properties-to-be-overridden.
2) Could you elaborate on this:
>
> > The renaming performed by resolve is shallow: it only changes the binding
> of the property. If the property is a method, it will not change references
> to the renamed identifier within the method body.
>
> It seems to me that this 'feature' could lead to more magic than mixins.
> If I read this correctly, overriding a method in a trait will be visible to
> methods outside the trait, but not to methods inside the trait. That
> doesn't seem to follow the original traits composition principles.
>
Ok, my bad. I should have made this more clear. It is not the case that
overriding the method is only visible outside of the trait, it will also be
visible inside the trait (so we don't break any trait composition principles
here). For example, given:
var t = Trait({
foo: function() { ... },
bar: function() { ... this.foo() ... }
})
Then, Trait.resolve({ foo: 'baz' }, t) results in:
Trait({
baz: function() { ... },
bar: function() { ... this.foo() ... } // references to 'foo' in method
bodies are not renamed, but internal methods do 'see' the new 'baz' method
foo: Trait.required // since 'foo' was renamed and this trait may depend
on it, a composer must provide a new definition for 'foo'
})
This is completely in line with the original trait composition semantics. In
particular, it upholds the trait "flattening property" that trait
composition can be performed statically without transforming method bodies.
3) In our usage, being able to test that an object "is" an instance of a
> mixin is quite important. In your description, you imply that type testing
> is an exercise left to the future. Has this not been an issue in applying
> your traits library?
>
My personal impression is that type testing should completely orthogonal to
reuse and inheritance mechanisms. To put it in Java terms: I think "obj
instanceof type" is only OK if type is an interface, not a class. Since
Javascript does not have a built-in notion of 'interface' (other than
feature-testing an object for the methods it implements), I couldn't figure
out a good way to reconcile 'instanceof' and traits. Admittedly, for traits
like "Enumerable" and "Comparable", the trait is itself a useful abstract
type. But I can equally imagine traits being used to factor out more ad hoc
implementation code whose type you don't want to see propagated into objects
instantiated from it, in the interest of not leaking implementation details
to clients of those objects.
> 4) Your 'stateful trait' example shows why I would call 'class state' and
> 'each-subclass state' (with the latter recommended, but the former could
> have applications). Because your implementation allows any type of property
> (not just methods), you also have 'instance state' in your traits. I think
> this is a good thing. Perhaps you should make that more explicit, since it
> diverges from the traits references.
>
Thanks for pointing this out.
> 5) I've lost track of where Harmony is heading with classes, but do you
> have any thoughts on that? Do you see you traits as an alternative to any
> other class proposal, or would it be intended as an extension of classes (as
> the original traits proposal was), should they ever arrive?
>
I'm glad you ask this question. To be frank, I don't see what benefits
classes would bring in addition to traits.
As a unit of code reuse, trait composition is more flexible than class
inheritance.
As a unit of encapsulation, closures are more flexible than private instance
variables.
As a unit of instantiation, 'maker' functions are more flexible than class
constructors.
As a unit of implementation sharing, traits have the same properties as
classes, given a trait-aware implementation.
As a unit of classification, interface types are more flexible than class
types (of course ES doesn't have interfaces, so I think a solid
object-type-testing mechanism would be a more useful addition to ES than
classes would be).
In the original traits model, since traits are stateless, classes play an
additional role not fulfilled by traits: carrying instance variables.
However, in an extended trait model such as this one that allows traits to
define instance variables as well as methods, traits can now take over this
role from classes as well.
Please do correct me if I'm missing a role for which classes do provide
significant added value.
Cheers,
Tom
> P T Withington
> OpenLaszlo.ORG/Laszlo Systems
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20100217/be49af39/attachment-0001.html>
More information about the es-discuss
mailing list