It's our pleasure, thanks :-)
>
> We at Mozilla Jetpack team have borrowed your API design and have
> implement almost compatible traits library.
> I wanted to describe here changes we made to the API, explain
> reasoning and get some opinions from you. I would also encourage
> adobting those unless there are reasons for not to. I can also
> contribute patch in case you'll decide to adopt those changes.
>
> So here is my small list:
>
> 1. `Trait.create` instantiates objects that have all the methods
> bounded to the instance. It undoubtedly a nice thing to have on one
> hand but:
>
> - It is suboptimal in terms of object instantiation speed and memory
> footprint.
> - Bring somewhat different ideology to already available in ES.
> - Makes mixing traits with prototypical inheritance unintuitive.
I understand your points, others have raised similar issues on the
es-discuss list recently. Of course, you could use Object.create to
create unbound trait instances, but then you would not get the early
error checking for missing required and for conflicting properties. I
feel tempted to add a third method (next to Object.create and
Trait.create) that returns unbound instances but does do the necessary
trait conformance checks, although I think 3 creation methods is too
much. Another option could be for traits.js to 'monkey-patch'
Object.create to perform the trait conformance checks before creating
the object. I'm ambivalent about that approach since modifying
existing primitives should not be done lightly.
Clever. I do wonder: since you create a public 'facade' proxy, there
may be issues with identity tests. That is, the facade proxy is not
=== to the hidden trait instance. Also, if the hidden trait instance
ever returns 'this' from one of its methods, clients could still get a
direct reference to it.
>
> 2. Everytime you need to create an instance from trait you Have to
> type Trait.create(proto, MyTrait) but as non of us likes typing more
> then have to we took slightly different approach, instead of copying
> all the property descriptors to the blank map ({}) we do copy them to
> the Object.create(Trait.prototype) where Trait.prototype has two
> methods defined:
>
> Trait.prototype.toString = function toString() {
> return '[object ' + this.constructor.name + ']'
> }
> Trait.prototype.create = function create(proto) {
> return Trait.create(undefined == proto ? {} : proto, this)
> }
> Trait.prototype.resolve = function resolve(resolutions) {
> return Trait.resolve(resolutions, this)
> }
> This gives us some syntax sugar:
>
> const Sugar = Trait({
> ....
> })
>
> Sugar.create()
> Sugar.create(Ancestor.prototype)
Indeed, this syntax is much nicer. In an earlier design, I had
considered a similar syntax: create, compose, resolve, etc. were
methods defined on traits. However, the main reason we changed this
was to clearly separate the namespace of trait operations from the
namespace of the trait object being described. For example, in your
code, a bug could arise if one of your traits accidentally defines a
method named `create` or `resolve`. For example, what happens if we
define:
const Cookie = Trait.compose({ create: function() { return 'cookie
created'; } }); // using your semantics of Trait.compose
...
Cookie.create(); // this would unexpectedly access the Cookie's
'create' property rather than call Trait.prototype.create.
>
> 3. Another place where we added some more syntax sugar is in
> Trait.compose function. While in original implementation it explicitly
> requires Traits to be passed to it we allow passing proprerty maps as
> well to create Traits out of it. In fact only way to create a trait is
> actuall `Trait.compose`. It's pretty easy to distinguish weather or
> not argument is trait or property map due to change described in
> previous paragraph and simple statement: `arguments[i] instanceof
> Trait`. So with this change in sugared trait can be written as
> follows:
>
> const MoreSugare = Trait.compose(Sugar
> , MyTrait
> , AnotherTrait
> , { name: 'anonymus trait'
> , foo: 'bar'
> }
> )
That's useful. In my implementation, since trait values really are
just property descriptor maps, I can't distinguish normal objects from
traits.
Perhaps I could make it work if I also create trait objects as
Object.create(Trait.prototype) instead of as normal empty objects. As
long as Trait.prototype is an empty object, there can be no confusion
of namespaces as in point 2) above.
Ok, I see. Because in your version, "Trait.compose({...})" is
essentially the same as "Trait({...})" in my version, you can use the
Trait constructor for turning property descriptor maps into traits.
Since property descriptor maps _are_ traits in my version, I don't
have a need for converting them. I guess your Trait constructor needs
to make sure that the property descriptor map now inherits from
Trait.prototype to inherit `create` and `resolve`.
>
> That's it. I'm actually thinking of a ways to merge Trait function
> with Trait.compose or of swapping them around but have not done it so
> far. Hope to hear your feedback!!
I understand the reason behind the changes you made, and all in all it
probably makes the library more lightweight to use. The fundamental
problem in the design I think, is the confusion of namespaces so that
you can't really create trait objects with methods named `create` and
`resolve`. You have to think of them as special 'keywords', even
though they are not. While I think in practice this will hardly ever
be a real problem, it still bothers me :-) One solution could be to
have Trait.compose check whether the trait defines properties named
`create` or `resolve` and to disallow this, but that would add yet
more runtime costs to trait creation.
There is a way to keep the advantage of defining `create` and
`resolve` as methods on the trait, and to separate the namespaces to
avoid confusion, if you define a "trait" to be a new special value
that e.g. has a pointer to its property descriptor map. But then
traits are no longer the same as normal property descriptor maps. I
really like the fact that in traits.js, a trait is just a property
descriptor map, not a special new kind of object.
That said, I will think about some of the features you presented. I
had not before considered the idea of having trait property descriptor
maps inherit from Trait.prototype or another object, so that we can
actually distinguish "trait" property descriptor maps from other
objects.
Thanks a lot for your feedback!
Cheers,
Tom
>I understand the reason behind the changes you made, and all in all it
> That's it. I'm actually thinking of a ways to merge Trait function
> with Trait.compose or of swapping them around but have not done it so
> far. Hope to hear your feedback!!
probably makes the library more lightweight to use. The fundamental
problem in the design I think, is the confusion of namespaces so that
you can't really create trait objects with methods named `create` and
`resolve`. You have to think of them as special 'keywords', even
though they are not. While I think in practice this will hardly ever
be a real problem, it still bothers me :-) One solution could be to
have Trait.compose check whether the trait defines properties named
`create` or `resolve` and to disallow this, but that would add yet
more runtime costs to trait creation.
There is a way to keep the advantage of defining `create` and
`resolve` as methods on the trait, and to separate the namespaces to
avoid confusion, if you define a "trait" to be a new special value
that e.g. has a pointer to its property descriptor map. But then
traits are no longer the same as normal property descriptor maps. I
really like the fact that in traits.js, a trait is just a property
descriptor map, not a special new kind of object.
That said, I will think about some of the features you presented. I
had not before considered the idea of having trait property descriptor
maps inherit from Trait.prototype or another object, so that we can
actually distinguish "trait" property descriptor maps from other
objects.
Thanks a lot for your feedback!
Before getting into the specifics, I'd like ask, if you had <http://wiki.ecmascript.org/doku.php?id=strawman:syntax_for_efficient_traits> and it were efficient, would that be adequate to address your concerns?
Hi Mark,
I've seen proposal but so far I have a mixed feelings about it, not sure I like it or not but I'm going to speak up on es mailing list once I'll make up my mind about it :)Before getting into the specifics, I'd like ask, if you had <http://wiki.ecmascript.org/doku.php?id=strawman:syntax_for_efficient_traits> and it were efficient, would that be adequate to address your concerns?