"Syntax for Efficient Traits" is now ready for discussion (was: Classes as Sugar is...)
On 13.09.2010 21:13, Mark S. Miller wrote:
Responding to Brendan's challenge at esdiscuss/2010-September/011825, I've been working up strawman:syntax_for_efficient_traits. Given the revival of that thread today, now seems like an opportune time to post it.
Seeing as how classes-as-sugar is getting a much cooler reception on the list than traits, if there are no objections, I propose to use the timeslot on the agenda currently reserved from classes-as-sugar to instead discuss traits and traits vs classes-as-sugar.
"Syntax for Efficient Traits" at strawman:syntax_for_efficient_traits is now ready for discussion.
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think /=>/ and /trait class/ are not needed.
/trait class EnumerableTrait() => {/
instead better:
trait Enumerable {
/class Interval(min, max) => {/
instead better:
class Interval(min, max) {
or even (with initialize method)
class Interval { function initialize(min, max) {} }
Superfluous symbols are not needed.
And still, don't forget about the (vertical) inheritance, i.e. class Foo may /extends/ Bar. If there will be no (delegation based) inheritance, such classes won't be needed, because they will be just casual pattern-factories. In addition, mixins/traits, being a horizontal inheritance (the augmentation of the vertical code reuse) are also good to have as delegation based. I think that you (and Tom) used statically augmented (a method per instance) object not because you like it ideologically, but because it's not possible to implement it cross-browser in nowadays.
Dmitry.
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
-
I used "trait class" rather than "trait" for two reasons:
-
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
-
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
-
-
I introduced "=>" to disambiguate whether the "{" following the (trait)
class head begins a block or a literal.
trait class EnumerableTrait() => {
instead better:
trait Enumerable {
Enumerable is a function for making traits. Your syntax suggests that Enumerable is itself a trait.
Without the "=>", how do we know that the "{" begins a literal rather than
block? (See the second TraitBody production).
class Interval(min, max) => {
instead better:
class Interval(min, max) {
or even (with initialize method)
class Interval { function initialize(min, max) {} }
Superfluous symbols are not needed.
Initialize methods are superfluous as well. Also, they introduce namespace confusion and re-initialization hazards.
And still, don't forget about the (vertical) inheritance, i.e. class Foo may extends Bar. If there will be no (delegation based) inheritance, such classes won't be needed, because they will be just casual pattern-factories. In addition, mixins/traits, being a horizontal inheritance (the augmentation of the vertical code reuse) are also good to have as delegation based. I think that you (and Tom) used statically augmented (a method per instance) object not because you like it ideologically, but because it's not possible to implement it cross-browser in nowadays.
I'm not sure what you mean. Could you please expand and clarify the question? Thanks.
On Mon, Sep 13, 2010 at 2:02 PM, Mark S. Miller <erights at google.com> wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
Oops. Should be "the object bound to ..."
On Sep 13, 2010, at 2:02 PM, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
I'd prefer 'class trait' which i think reads better, but i'm not sure how much i'm biased due to my desire to retain LL(1)-ness (I know that there is some bias at least)
On Mon, Sep 13, 2010 at 5:18 PM, Oliver Hunt <oliver at apple.com> wrote:
On Sep 13, 2010, at 2:02 PM, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
I'd prefer 'class trait' which i think reads better, but i'm not sure how much i'm biased due to my desire to retain LL(1)-ness (I know that there is some bias at least)
Given that we're not treating "trait" as a keyword, when we see
class trait(x, y) => {...}
how would we know whether this is an anonymous TraitClassExpression on a named ClassExpression for the class named "trait"? I agree we could make a special case for "trait" appearing in that position, but is that more or less unpleasant that the lookahead needed to distinguish "trait class"?
Also, to my ears, "trait class" reads like "adjective noun". A "class" is a thing for making the kinds of things it describes. A "trait class" is a thing which makes the traits it describes. For the same reason, several people have suggested "const function" and no one has suggested "function const".
On Mon, Sep 13, 2010 at 5:27 PM, Mark S. Miller <erights at google.com> wrote:
On Mon, Sep 13, 2010 at 5:18 PM, Oliver Hunt <oliver at apple.com> wrote:
On Sep 13, 2010, at 2:02 PM, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
I'd prefer 'class trait' which i think reads better, but i'm not sure how much i'm biased due to my desire to retain LL(1)-ness (I know that there is some bias at least)
Given that we're not treating "trait" as a keyword, when we see
class trait(x, y) => {...}
how would we know whether this is an anonymous TraitClassExpression on a named ClassExpression for the class named "trait"?
Oops. That should be "...TraitClassExpression or a named ClassExpression..."
I agree we could make a special case for "trait" appearing in that position, but is that more or less unpleasant that the lookahead needed to distinguish "trait class"?
"that" should be "than"
Thanks for kickstarting this proposal. Initial comments:
-
The keyword 'mixin' could be confusing. It is used to express trait composition, not mixin composition. A better alternative would be "compose". If we want to stick to reserved keywords, "implements" seems the most appropriate (although this similarly confuses trait composition with interface implementation).
-
I like your semantics of raising trait composition errors upon evaluation of a (trait) class expression/declaration. As you note, typically most class definitions will occur at top level, and composition errors can be early errors. Yet it does not preclude nesting a (trait) class expression, such that traits can enjoy the same flexibility as functions.
"These examples suggest that perhaps our syntax should implicitly compose where it currently implicitly overrides. This is doable, but leaves open the question of how to syntactically express an override." => In the original Smalltalk Traits, methods declared in the class always
implicitly overrode methods declared in traits. This keeps the model closer to traditional OOP inheritance. I would not mind switching the default from 'override' to 'compose'. In that case, perhaps one could express overriding by replacing the 'method' keyword with an 'override' keyword, as so:
class Foo(a,b) { mixin BarTrait(), override toString() { ... } // overrides BarTrait.toString if BarTrait defines it }
Perhaps the override modifier could even cause an error if there is no method to override.
Cheers, Tom
2010/9/14 Mark S. Miller <erights at google.com>
On 2010-09-14, at 04:56, Tom Van Cutsem wrote:
If we want to stick to reserved keywords, "implements" seems the most appropriate (although this similarly confuses trait composition with interface implementation).
Alternatively, you could repurpose the (now disparaged) with
keyword. That's what OpenLaszlo uses for its paint:
class sundae extends icecream with jimmies, chocolatesauce, cherry implements dessert;
On 14.09.2010 1:03, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 2:02 PM, Mark S. Miller <erights at google.com <mailto:erights at google.com>> wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com <mailto:dmitry.soshnikov at gmail.com>> wrote: I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think /=>/ and /trait class/ are not needed. * I used "trait class" rather than "trait" for two reasons: 1) Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
This keyword would look interesting though. Traits/mixins concept is a weighty thing and if to reproduce it, then in a good way -- easy to use/write and syntactically minimalistic. By the way, JFTR, PHP also now providing traits (wiki.php.net/rfc/horizontalreuse) with using this /trait TraitName {}/ syntax; Scala too has such syntax -- i.e. without (trait class, class trait) referencing to parser issues.
2) More importantly, the object binds to the name it declares is not a trait but a function for making traits.
Oops. Should be "the object bound to ..."
* I introduced "=>" to disambiguate whether the "{" following the (trait) class head begins a block or a literal.
Yeah, but maybe somehow it (ambiguous) may be avoided.
/trait class EnumerableTrait() => {/ instead better: trait Enumerable { Enumerable is a function for making traits. Your syntax suggests that Enumerable is itself a trait.
Hm, "function for making trait", "trait itself". Too many things. It may bring complexity. Yes, i thought (and see in other languages, the same Ruby) that Enumerable is a trait itself, and not a special (non-needed to me) function. A user needed just a trait, not a function + trait.
Without the "=>", how do we know that the "{" begins a literal rather than block? (See the second TraitBody production). /class Interval(min, max) => {/ instead better: class Interval(min, max) { or even (with initialize method) class Interval { function initialize(min, max) {} } Superfluous symbols are not needed. Initialize methods are superfluous as well. Also, they introduce namespace confusion and re-initialization hazards.
Yep, but how then you propose to initialize an instance? I mean, not a state, but the initial code. In the class body?
And still, don't forget about the (vertical) inheritance, i.e. class Foo may /extends/ Bar. If there will be no (delegation based) inheritance, such classes won't be needed, because they will be just casual pattern-factories. In addition, mixins/traits, being a horizontal inheritance (the augmentation of the vertical code reuse) are also good to have as delegation based. I think that you (and Tom) used statically augmented (a method per instance) object not because you like it ideologically, but because it's not possible to implement it cross-browser in nowadays. I'm not sure what you mean. Could you please expand and clarify the question? Thanks.
I mentioned "classes-as-sugar" proposal, where a class cannot inherit (making a vertical inheritance chain).
A -> B -> C
In addition, if any class may mixin several traits/mixins, we have a horizontal inheritance:
A -> B -> C | | | |----------- m1 -> m3 | |--m1 -> m2
i.e. at every link, first the whole chain of mixins is considered and only after that we go to the next (vertical) link. Sure, the normalized inheritance chain for resolution is linear:
A -> m1 -> m2 -> B -> m1 -> m3 -> C
I.e. an instance always inherits methods/properties from mixins or classes, but not has own (as in your implementation of classes and traits). The thing is not only in optimizing VM, but in dynamics of a language. I.e. if we modify somehow methods in the class or trait, all instances via delegation will have a new version.
That what Ruby uses:
module MyTrait def my_method print(@items) end end
class MyCollection
mixin MyTrait to the class
include MyTrait
and also some built-in mixin
include Enumerable
def initialize(data) @data = data end
method required by the
Enumerable trait/mixin
def each @data.each { |i| yield(i) } end
end
an intance
things = MyCollection.new(['x','yz','defgh','ij','klmno'])
thigns.my_method # from MyTrait
available from Enumerable trait
print(things.min) #=> "defgh"
print(things.max) #=> "yz"
aka Array#map in JS
print(things.collect { |i| i.upcase }) #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"]
modules/traits are open
module Enumerable def new_method print(@items, " from new method") end end
a new method is available
for the instance via delegation
things.new_method #=> ['x','yz','defgh','ij','klmno'] from new method
P.S.: Why am I asking? -- just see that you everywhere use static/const and frozen things. Want to know, do you propose JS to be a static language without opened classes/protos/traits/etc, "monkey-patching", etc.?
Dmitry.
On Sep 13, 2010, at 5:27 PM, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 5:18 PM, Oliver Hunt <oliver at apple.com> wrote:
On Sep 13, 2010, at 2:02 PM, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
More importantly, the object binds to the name it declares is not a trait but a function for making traits.
I'd prefer 'class trait' which i think reads better, but i'm not sure how much i'm biased due to my desire to retain LL(1)-ness (I know that there is some bias at least)
Given that we're not treating "trait" as a keyword, when we see
class trait(x, y) => {...}
how would we know whether this is an anonymous TraitClassExpression on a named ClassExpression for the class named "trait"? I agree we could make a special case for "trait" appearing in that position, but is that more or less unpleasant that the lookahead needed to distinguish "trait class"?
face palm you are indeed correct :D
Also, to my ears, "trait class" reads like "adjective noun". A "class" is a thing for making the kinds of things it describes. A "trait class" is a thing which makes the traits it describes. For the same reason, several people have suggested "const function" and no one has suggested "function const".
I think this may just be a byproduct of us thinking about them in different ways -- but that's moot as your above point is clearly correct :D
On Tue, Sep 14, 2010 at 4:47 AM, P T Withington <ptw at pobox.com> wrote:
On 2010-09-14, at 04:56, Tom Van Cutsem wrote:
If we want to stick to reserved keywords, "implements" seems the most appropriate (although this similarly confuses trait composition with interface implementation).
In the default remains override, "extends" seems more natural than "implements".
Alternatively, you could repurpose the (now disparaged)
with
keyword. That's what OpenLaszlo uses for its paint:class sundae extends icecream with jimmies, chocolatesauce, cherry implements dessert;
Hmmm. "with" reads well, no matter whether the default is compose or
override. And given its position, there's no ambiguity with its other legacy uses. Good suggestion!
Btw, the current proposal does currently repurpose "with" for renamings. Even though there's no syntactic conflict, if we use "with" instead of "mixin" we should choose a different syntax for renamings. Suggestions?
On Tue, Sep 14, 2010 at 8:46 AM, Mark S. Miller <erights at google.com> wrote:
Btw, the current proposal does currently repurpose "with" for renamings. Even though there's no syntactic conflict, if we use "with" instead of "mixin" we should choose a different syntax for renamings. Suggestions?
"as".
Mike
On Tue, Sep 14, 2010 at 6:37 AM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
On 14.09.2010 1:03, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 2:02 PM, Mark S. Miller <erights at google.com>wrote:
On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think => and trait class are not needed.
I used "trait class" rather than "trait" for two reasons:
- Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
This keyword would look interesting though. Traits/mixins concept is a weighty thing and if to reproduce it, then in a good way -- easy to use/write and syntactically minimalistic. By the way, JFTR, PHP also now providing traits (wiki.php.net/rfc/horizontalreuse) with using this trait TraitName {} syntax; Scala too has such syntax -- i.e. without (trait class, class trait) referencing to parser issues.
JFTR?
PHP and Scala can tolerate a much higher migration tax than JavaScript because of the differences in how they're deployed. I hesitate to propose any new keywords beyond those reserved by ES5/strict. All the proposals on < harmony:proposals> and the current
traits strawman are upwards compatible from ES5/strict, allowing a conforming implementation to provide them with no further opt-in than "use strict". Not all the strawman have this virtue, some with good reason. But to ease incremental prototyping and adoption, we should preserve this virtue when we can.
2) More importantly, the object binds to the name it declares is not
a trait but a function for making traits.
Oops. Should be "the object bound to ..."
- I introduced "=>" to disambiguate whether the "{" following the (trait) class head begins a block or a literal.
Yeah, but maybe somehow it (ambiguous) may be avoided.
Somehow sure. Suggestions appreciated.
trait class EnumerableTrait() => {
instead better:
trait Enumerable {
Enumerable is a function for making traits. Your syntax suggests that Enumerable is itself a trait.
Hm, "function for making trait", "trait itself". Too many things. It may bring complexity. Yes, i thought (and see in other languages, the same Ruby) that Enumerable is a trait itself, and not a special (non-needed to me) function. A user needed just a trait, not a function + trait.
Please check out traitsjs.org. The notion of traits there and in
this strawman is the "stateful traits" approach that Tom first explained at prog.vub.ac.be/Publications/2009/vub-prog-tr-09-04.pdf. Until I saw
this paper, I had written traits off as a dead end.
Without the "=>", how do we know that the "{" begins a literal rather than block? (See the second TraitBody production).
class Interval(min, max) => {
instead better:
class Interval(min, max) {
or even (with initialize method)
class Interval { function initialize(min, max) {} }
Superfluous symbols are not needed.
Initialize methods are superfluous as well. Also, they introduce namespace confusion and re-initialization hazards.
Yep, but how then you propose to initialize an instance? I mean, not a state, but the initial code. In the class body?
In the "Program" portion of the second TraitBody production. This can
declare new variables that are captured by the following TraitLiteral. The lexical variables captured by each TraitLiteral is the state of that trait. The union of the states of the traits that a class composes together is the state of an instance.
And still, don't forget about the (vertical) inheritance, i.e. class Foo may extends Bar. If there will be no (delegation based) inheritance, such classes won't be needed, because they will be just casual pattern-factories. In addition, mixins/traits, being a horizontal inheritance (the augmentation of the vertical code reuse) are also good to have as delegation based. I think that you (and Tom) used statically augmented (a method per instance) object not because you like it ideologically, but because it's not possible to implement it cross-browser in nowadays.
I'm not sure what you mean. Could you please expand and clarify the question? Thanks.
I mentioned "classes-as-sugar" proposal, where a class cannot inherit (making a vertical inheritance chain).
A -> B -> C
In addition, if any class may mixin several traits/mixins, we have a horizontal inheritance:
A -> B -> C | | | |----------- m1 -> m3 | |--m1 -> m2
i.e. at every link, first the whole chain of mixins is considered and only after that we go to the next (vertical) link. Sure, the normalized inheritance chain for resolution is linear:
A -> m1 -> m2 -> B -> m1 -> m3 -> C
I.e. an instance always inherits methods/properties from mixins or classes, but not has own (as in your implementation of classes and traits). The thing is not only in optimizing VM, but in dynamics of a language. I.e. if we modify somehow methods in the class or trait, all instances via delegation will have a new version.
That what Ruby uses:
module MyTrait def my_method print(@items) end end
class MyCollection
mixin MyTrait to the class
include MyTrait
and also some built-in mixin
include Enumerable
def initialize(data) @data = data end
method required by the
Enumerable trait/mixin
def each @data.each { |i| yield(i) } end
end
an intance
things = MyCollection.new(['x','yz','defgh','ij','klmno'])
thigns.my_method # from MyTrait
available from Enumerable trait
print(things.min) #=> "defgh" print(things.max) #=> "yz"
aka Array#map in JS
print(things.collect { |i| i.upcase }) #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"]
modules/traits are open
module Enumerable def new_method print(@items, " from new method") end end
a new method is available
for the instance via delegation
things.new_method #=> ['x','yz','defgh','ij','klmno'] from new method
I'm still not sure I understand you correctly. But if I do, Override is your vertical composition and Compose is your horizontal composition. Via Object.create, one can also mix traits with vertical composition using the JavaScript prototype chain. But then unbound inherited methods become observable, leading to all the normal confusions. By using only Override for vertical composition, only bound methods are observable. (This is like the old "binding on extraction" property of old ES4 classes.)
P.S.: Why am I asking? -- just see that you everywhere use static/const and frozen things. Want to know, do you propose JS to be a static language without opened classes/protos/traits/etc, "monkey-patching", etc.?
Not at all. As previously discussed, JavaScript will lose none of its support for monkey patching, low integrity patterns, unfrozen objects, etc. None of these proposals take any of those away.
Further, trait classes can be instantiated with Object.create, so traits even enhance the expressiveness of low integrity patterns and vertical composition using prototypes if that's what you wish. Adapting a previous example:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = Object.create(
SuperPoint.prototype,
StatelessPointTrait());
JFTR?
Just For The Record.
Googling "urban dictionary: <phrase I don't recognize>" usually works. JFYI. FWIW. YMMV. ;)
On 14.09.2010 20:19, Mark S. Miller wrote:
On Tue, Sep 14, 2010 at 6:37 AM, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com <mailto:dmitry.soshnikov at gmail.com>> wrote:
On 14.09.2010 1:03, Mark S. Miller wrote:
On Mon, Sep 13, 2010 at 2:02 PM, Mark S. Miller <erights at google.com <mailto:erights at google.com>> wrote: On Mon, Sep 13, 2010 at 1:18 PM, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com <mailto:dmitry.soshnikov at gmail.com>> wrote: I didn't finished a detailed reading yet, but from the brief scanning, syntactically, I think /=>/ and /trait class/ are not needed. * I used "trait class" rather than "trait" for two reasons: 1) Syntactic ambiguity fear. "trait" is not one of the identifiers reserved by ES5 or ES5/strict, and so I am not proposing that it be an ES-Harmony keyword.
This keyword would look interesting though. Traits/mixins concept is a weighty thing and if to reproduce it, then in a good way -- easy to use/write and syntactically minimalistic. By the way, JFTR, PHP also now providing traits (http://wiki.php.net/rfc/horizontalreuse) with using this /trait TraitName {}/ syntax; Scala too has such syntax -- i.e. without (trait class, class trait) referencing to parser issues.
JFTR?
Sorry -- "just for the record", as already Dave has mentioned.
PHP and Scala can tolerate a much higher migration tax than JavaScript because of the differences in how they're deployed. I hesitate to propose any new keywords beyond those reserved by ES5/strict. All the proposals on harmony:proposals and the current traits strawman are upwards compatible from ES5/strict, allowing a conforming implementation to provide them with no further opt-in than "use strict". Not all the strawman have this virtue, some with good reason. But to ease incremental prototyping and adoption, we should preserve this virtue when we can.
Yeah, yeah, I know, "backward compats". Forget that they are rule.
2) More importantly, the object binds to the name it declares is not a trait but a function for making traits. Oops. Should be "the object bound to ..." * I introduced "=>" to disambiguate whether the "{" following the (trait) class head begins a block or a literal.
Yeah, but maybe somehow it (ambiguous) may be avoided.
Somehow sure. Suggestions appreciated.
/trait class EnumerableTrait() => {/ instead better: trait Enumerable { Enumerable is a function for making traits. Your syntax suggests that Enumerable is itself a trait.
Hm, "function for making trait", "trait itself". Too many things. It may bring complexity. Yes, i thought (and see in other languages, the same Ruby) that Enumerable is a trait itself, and not a special (non-needed to me) function. A user needed just a trait, not a function + trait.
Please check out traitsjs.org. The notion of traits there and in this strawman is the "stateful traits" approach that Tom first explained at prog.vub.ac.be/Publications/2009/vub-prog-tr-09-04.pdf. Until I saw this paper, I had written traits off as a dead end.
It seems something interesting there, thanks, I'll take a look on the paper (as well as on all examples on the site). However, I'm aware that concept of a "trait" in contrast of a "mixin" has no state to avoid conflicts.
Without the "=>", how do we know that the "{" begins a literal rather than block? (See the second TraitBody production). /class Interval(min, max) => {/ instead better: class Interval(min, max) { or even (with initialize method) class Interval { function initialize(min, max) {} } Superfluous symbols are not needed. Initialize methods are superfluous as well. Also, they introduce namespace confusion and re-initialization hazards.
Yep, but how then you propose to initialize an instance? I mean, not a state, but the initial code. In the class body?
In the "Program" portion of the second TraitBody production. This can declare new variables that are captured by the following TraitLiteral. The lexical variables captured by each TraitLiteral is the state of that trait. The union of the states of the traits that a class composes together is the state of an instance.
With a state it's OK. But usually, an initialization process may include internal methods calling (which of course also just create a state properties, however, there is a difference -- conveniently to call three methods, which will set 10 properties, or to define these 10 properties manually in the body of a class).
And still, don't forget about the (vertical) inheritance, i.e. class Foo may /extends/ Bar. If there will be no (delegation based) inheritance, such classes won't be needed, because they will be just casual pattern-factories. In addition, mixins/traits, being a horizontal inheritance (the augmentation of the vertical code reuse) are also good to have as delegation based. I think that you (and Tom) used statically augmented (a method per instance) object not because you like it ideologically, but because it's not possible to implement it cross-browser in nowadays. I'm not sure what you mean. Could you please expand and clarify the question? Thanks.
I mentioned "classes-as-sugar" proposal, where a class cannot inherit (making a vertical inheritance chain). A -> B -> C In addition, if any class may mixin several traits/mixins, we have a horizontal inheritance: A -> B -> C | | | |----------- m1 -> m3 | |--m1 -> m2 i.e. at every link, first the whole chain of mixins is considered and only after that we go to the next (vertical) link. Sure, the normalized inheritance chain for resolution is linear: A -> m1 -> m2 -> B -> m1 -> m3 -> C I.e. an instance always inherits methods/properties from mixins or classes, but not has own (as in your implementation of classes and traits). The thing is not only in optimizing VM, but in dynamics of a language. I.e. if we modify somehow methods in the class or trait, all instances via delegation will have a new version. That what Ruby uses: module MyTrait def my_method print(@items) end end class MyCollection # mixin MyTrait to the class include MyTrait # and also some built-in mixin include Enumerable def initialize(data) @data = data end # method required by the # Enumerable trait/mixin def each @data.each { |i| yield(i) } end end # an intance things = MyCollection.new(['x','yz','defgh','ij','klmno']) thigns.my_method # from MyTrait # available from Enumerable trait print(things.min) #=> "defgh" print(things.max) #=> "yz" # aka Array#map in JS print(things.collect { |i| i.upcase }) #=> ["X", "YZ", "DEFGH", "IJ", "KLMNO"] # modules/traits are open module Enumerable def new_method print(@items, " from new method") end end # a new method is available # for the instance via delegation things.new_method #=> ['x','yz','defgh','ij','klmno'] from new method
I'm still not sure I understand you correctly. But if I do, Override is your vertical composition and Compose is your horizontal composition. Via Object.create, one can also mix traits with vertical composition using the JavaScript prototype chain. But then unbound inherited methods become observable, leading to all the normal confusions. By using only Override for vertical composition, only bound methods are observable. (This is like the old "binding on extraction" property of old ES4 classes.)
Yes, regarding the end state of an object (via override/composition) you understand it correctly. But I meant exactly delegation based inheritance and asked -- why do you have (propose) a scheme where an object will have /own/ properties but not inherited? In Ruby as I showed, if we change Enumerable module (read Enumerable trait/mixin) by adding a new method "new_method" then the instance "things" of the class "MyCollection" automatically via delegation has access to the new method.
And regarding classes, I think it's not acceptable not to have inheritance and to create also own properties. Even if will be a good VM optimization, the dynamics of the languages is lost -- if we add a new method to a class -- will all instances see this new method? That exactly I'm asking.
P.S.: Why am I asking? -- just see that you everywhere use static/const and frozen things. Want to know, do you propose JS to be a static language without opened classes/protos/traits/etc, "monkey-patching", etc.?
Not at all. As previously discussed, JavaScript will lose none of its support for monkey patching, low integrity patterns, unfrozen objects, etc. None of these proposals take any of those away.
Just to clarify and not to confuse terminology/meanings, do I understand correctly: "low integrity " -- non-|this|-bounded inherited functions, and "high integrity" -- vice-versa -- statically bound |this| and own properties? Thanks.
Further, trait classes can be instantiated with Object.create, so traits even enhance the expressiveness of low integrity patterns and vertical composition using prototypes if that's what you wish. Adapting a previous example:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = Object.create( SuperPoint.prototype, StatelessPointTrait());
Yeah, I see. But again, instantiation of a trait creates own properties of an instance, but not just adds additional link for delegation.
By the way, the phrase "a trait instantiate" in general case contradicts to traits concept. Because in many (all?) implementations and some general definitions of traits -- traits in contrast with classes cannot instantiate objects. They are just additional modules (to add small traits for the instance).
Hm, "function for making trait", "trait itself". Too many things. It may
bring complexity. Yes, i thought (and see in other languages, the same Ruby) that Enumerable is a trait itself, and not a special (non-needed to me) function. A user needed just a trait, not a function + trait.
Please check out traitsjs.org. The notion of traits there and in this strawman is the "stateful traits" approach that Tom first explained at prog.vub.ac.be/Publications/2009/vub-prog-tr-09-04.pdf. Until I saw this paper, I had written traits off as a dead end.
It seems something interesting there, thanks, I'll take a look on the paper (as well as on all examples on the site). However, I'm aware that concept of a "trait" in contrast of a "mixin" has no state to avoid conflicts.
It is true that original traits are always stateless. This is not so much to avoid name conflicts as to avoid the whole problem of diamond inheritance (duplicate state inherited via different inheritance paths, cf. C++'s virtual inheritance). On the other hand, stateless traits have their drawbacks: you need to pollute a trait's public API with getter/setter methods to 'fake' state.
The key idea behind the Traits discussed here and in that paper is that they are 'generative': you generate them by calling a function that returns new traits. This allows these traits to capture lexically visible state (usually the arguments of the generator function). Using this approach you can make stateful traits, and apart from instantiation issues (see below), there is no need to distinguish a class from a trait anymore.
I'm still not sure I understand you correctly. But if I do, Override is your vertical composition and Compose is your horizontal composition. Via Object.create, one can also mix traits with vertical composition using the JavaScript prototype chain. But then unbound inherited methods become observable, leading to all the normal confusions. By using only Override for vertical composition, only bound methods are observable. (This is like the old "binding on extraction" property of old ES4 classes.)
Yes, regarding the end state of an object (via override/composition) you understand it correctly. But I meant exactly delegation based inheritance and asked -- why do you have (propose) a scheme where an object will have /own/ properties but not inherited? In Ruby as I showed, if we change Enumerable module (read Enumerable trait/mixin) by adding a new method "new_method" then the instance "things" of the class "MyCollection" automatically via delegation has access to the new method.
And regarding classes, I think it's not acceptable not to have inheritance and to create also own properties. Even if will be a good VM optimization, the dynamics of the languages is lost -- if we add a new method to a class -- will all instances see this new method? That exactly I'm asking.
Ruby's modules and instance-class relationships indeed work via delegation and enable changes to all live instances by changing modules/classes, and per-instance customization. OTOH, because they chain together objects/classes/modules via delegation instead of flattening the properties, they don't provide the early conflict detection properties of traits. I think you can get either one or the other, but not both from 1 language feature. Javascript already has prototype delegation. Adding traits would allow us to express object composition in different ways, with different tradeoffs.
Just to clarify and not to confuse terminology/meanings, do I understand correctly: "low integrity " -- non-|this|-bounded inherited functions, and "high integrity" -- vice-versa -- statically bound |this| and own properties? Thanks.
I think this is the case + low-integrity = non-frozen object and high integrity = frozen object.
By the way, the phrase "a trait instantiate" in general case contradicts to traits concept. Because in many (all?) implementations and some general definitions of traits -- traits in contrast with classes cannot instantiate objects. They are just additional modules (to add small traits for the instance).
Indeed, for original traits, "instantiating a trait" makes little sense. Original traits need classes to provide state. As indicated above, generative traits can capture state, and if they don't have any missing required properties, it could make sense to instantiate them.
In MarkM's proposal, a "trait class" is a function that, when called, returns a property descriptor map. A "class" is a function that, when called, returns an object created from a property descriptor map. While technically you can "instantiate" a trait by calling a trait class function, you will end up with a property descriptor map, not an instance. From the end-user's point of view, it still only makes sense to instantiate class functions.
On 15.09.2010 1:39, Tom Van Cutsem wrote:
Hm, "function for making trait", "trait itself". Too many things. It may bring complexity. Yes, i thought (and see in other languages, the same Ruby) that Enumerable is a trait itself, and not a special (non-needed to me) function. A user needed just a trait, not a function + trait. Please check out <http://traitsjs.org/>. The notion of traits there and in this strawman is the "stateful traits" approach that Tom first explained at <http://prog.vub.ac.be/Publications/2009/vub-prog-tr-09-04.pdf>. Until I saw this paper, I had written traits off as a dead end.
It seems something interesting there, thanks, I'll take a look on the paper (as well as on all examples on the site). However, I'm aware that concept of a "trait" in contrast of a "mixin" has no state to avoid conflicts.
It is true that original traits are always stateless. This is not so much to avoid name conflicts as to avoid the whole problem of diamond inheritance (duplicate state inherited via different inheritance paths, cf. C++'s virtual inheritance).
Yeah, I see. Though, in ES it should be quite clear: there is one (vertical) prototype chain; at every link of the prototype chain, can be several included traits/mixins (i.e. horizontal chain of mixins). Thus, the last included module -- overrides (shadows) the previously included one. The same again is made in Ruby. At last, the whole inheritance chain (vertical + horizontal) is linear.
I.e. possibly the diamond problem should not be solving in case of mixins/traits at all. Because traits/mixins are /exactly small traits/ (small additional part of functionality), which may be /completely independent by semantics/ from the the vertical chain. And therefore there is no need to know -- which method to call. The answer is simple (in case of mixins) -- the last one included module -- overrides previously included module in respect of the same method name (thus, in Ruby, own a class's method with the same name overrides module's one).
It's the easiest way though (Python e.g. uses linearization too to avoid diamonds issue). The way with warning a user about naming conflicts (in particular, in your implementation too) seems also good. From the other hand we don't warn a user if he overrides (in JS) a parent method with the same name.
On the other hand, stateless traits have their drawbacks: you need to pollute a trait's public API with getter/setter methods to 'fake' state.
I though (again, maybe in "original traits"?) that all needed state for a trait is provided by a class (which includes the trait). And the trait in contrast with a mixin do not provide (and create during execution of its methods) any additional object's state, creating new instance properties. Or you mean some additional (auxiliary) variables needed for a trait?
The key idea behind the Traits discussed here and in that paper is that they are 'generative': you generate them by calling a function that returns new traits.
Yeah, sort of Meta-Traits (constructors of traits).
This allows these traits to capture lexically visible state (usually the arguments of the generator function). Using this approach you can make stateful traits, and apart from instantiation issues (see below), there is no need to distinguish a class from a trait anymore.
Yes, I see. It seems interesting approach. Since you have all needed (auxiliary) variables for a trait and do not pollute (as a mixin) object's state. I.e. the object may even not to have required methods/state for the trait? All this may be passed as arguments for a meta-trait?
I'm still not sure I understand you correctly. But if I do, Override is your vertical composition and Compose is your horizontal composition. Via Object.create, one can also mix traits with vertical composition using the JavaScript prototype chain. But then unbound inherited methods become observable, leading to all the normal confusions. By using only Override for vertical composition, only bound methods are observable. (This is like the old "binding on extraction" property of old ES4 classes.)
Yes, regarding the end state of an object (via override/composition) you understand it correctly. But I meant exactly delegation based inheritance and asked -- why do you have (propose) a scheme where an object will have /own/ properties but not inherited? In Ruby as I showed, if we change Enumerable module (read Enumerable trait/mixin) by adding a new method "new_method" then the instance "things" of the class "MyCollection" automatically via delegation has access to the new method. And regarding classes, I think it's not acceptable not to have inheritance and to create also own properties. Even if will be a good VM optimization, the dynamics of the languages is lost -- if we add a new method to a class -- will all instances see this new method? That exactly I'm asking.
Ruby's modules and instance-class relationships indeed work via delegation and enable changes to all live instances by changing modules/classes, and per-instance customization. OTOH, because they chain together objects/classes/modules via delegation instead of flattening the properties, they don't provide the early conflict detection properties of traits. I think you can get either one or the other, but not both from 1 language feature. Javascript already has prototype delegation. Adding traits would allow us to express object composition in different ways, with different tradeoffs.
Yes, that exactly I was asking Mark. So the "issue" is that's not possible to have at the same time delegation base traits+protos and early conflict detection. I see. Don't know what is better. Such conflicts appear not often (and usually consciously are made by a programmer, i.e. "yes, I knew that previous mixin/trait also has such method, and I want to use the method from the second module. Therefore, I include first this module, and then -- the second one. If I wanted vice-versa, I'd included first the second module, and the the first one" -- that what is Ruby uses).
Just to clarify and not to confuse terminology/meanings, do I understand correctly: "low integrity " -- non-|this|-bounded inherited functions, and "high integrity" -- vice-versa -- statically bound |this| and own properties? Thanks.
I think this is the case + low-integrity = non-frozen object and high integrity = frozen object.
By the way, the phrase "a trait instantiate" in general case contradicts to traits concept. Because in many (all?) implementations and some general definitions of traits -- traits in contrast with classes cannot instantiate objects. They are just additional modules (to add small traits for the instance).
Indeed, for original traits, "instantiating a trait" makes little sense. Original traits need classes to provide state. As indicated above, generative traits can capture state, and if they don't have any missing required properties, it could make sense to instantiate them.
Yes, now I see the feature of the implementation.
In MarkM's proposal, a "trait class" is a function that, when called, returns a property descriptor map. A "class" is a function that, when called, returns an object created from a property descriptor map. While technically you can "instantiate" a trait by calling a trait class function, you will end up with a property descriptor map, not an instance. From the end-user's point of view, it still only makes sense to instantiate class functions.
Yes, I see. And then this property descriptor (an instantiated trait) is merged with the original object.
OK, Tom, thanks for your clarifications, it was useful.
The idea of "non-traditional available for instantiation traits" sounds interesting. However, the issue with non-inheritable classes is have to avoided. I may ask any JS-programmer -- whether he needed classes in JS additionally to the (already available "classes" as "constructor+prototype" pair) prototypes, and moreover, these classes /cannot inherit/ and every object created from a class will have /own methods/ (but delegated ones) -- they will answer -- "No".
Dmitry.
On 2010-09-14, at 17:39, Tom Van Cutsem wrote:
It is true that original traits are always stateless. This is not so much to avoid name conflicts as to avoid the whole problem of diamond inheritance (duplicate state inherited via different inheritance paths, cf. C++'s virtual inheritance).
Off topic, but the "problem of diamond inheritance" is a myth IMO. It come's from C++'s premature optimization[*] of property access in instances to base+offset and shared methods -- it is not an intrinsic problem of OOP. In many OOP languages (most with a Lisp lineage), what C++ calls "virtual inheritance" is the norm, not the exception.
Original traits, again IMO, propagated this myth by declining to include state.
[*] Or less generously, compiler's simplicity.
Yeah, I see. Though, in ES it should be quite clear: there is one (vertical) prototype chain; at every link of the prototype chain, can be several included traits/mixins (i.e. horizontal chain of mixins). Thus, the last included module -- overrides (shadows) the previously included one. The same again is made in Ruby. At last, the whole inheritance chain (vertical + horizontal) is linear.
I.e. possibly the diamond problem should not be solving in case of mixins/traits at all. Because traits/mixins are exactly small traits(small additional part of functionality), which may be completely independent by semantics from the the vertical chain. And therefore there is no need to know -- which method to call. The answer is simple (in case of mixins) -- the last one included module -- overrides previously included module in respect of the same method name (thus, in Ruby, own a class's method with the same name overrides module's one).
It's the easiest way though (Python e.g. uses linearization too to avoid diamonds issue). The way with warning a user about naming conflicts (in particular, in your implementation too) seems also good. From the other hand we don't warn a user if he overrides (in JS) a parent method with the same name.
Linearization is indeed a well-known approach to resolve the diamond, but it doesn't adequately 'solve' the problem from a software evolution point of view: small changes in one part of a large code base (i.e. changing the order of two 'include' or 'extends' statements) may end up impacting a totally different part of the code base - usually without warning. I can recommend Alan Snyder's classic 1986 OOPSLA paper titled "Encapsulation and Inheritance in Object-oriented Programming Languages", where he explains this and related issues with inheritance.
On the other hand, stateless traits have their drawbacks: you need to pollute a trait's public API with getter/setter methods to 'fake' state.
I though (again, maybe in "original traits"?) that all needed state for a trait is provided by a class (which includes the trait). And the trait in contrast with a mixin do not provide (and create during execution of its methods) any additional object's state, creating new instance properties. Or you mean some additional (auxiliary) variables needed for a trait?
Yes, you are right: original traits do not define instance properties. What I meant is that, if an original trait's provided methods depend on instance properties, then in principle the trait should require a getter/setter to manipulate those instance properties. This forces a class that uses an original trait to provide a (usually public) getter/setter method such that the trait can access the instance property. So actually I was incorrect: it's not the trait's public API that is polluted, but the class that uses trait's API (if the property was not intended to be visible to external clients in the first place).
The key idea behind the Traits discussed here and in that paper is that they are 'generative': you generate them by calling a function that returns new traits.
Yeah, sort of Meta-Traits (constructors of traits).
Indeed. Although I would stick with the more straightforward term 'trait generator'. The original Squeak Smalltalk traits implementation actually defined the concept of a MetaTrait (its role is to traits what metaclasses are to Smalltalk classes, but let's not go there ;-)
This allows these traits to capture lexically visible state (usually the arguments of the generator function). Using this approach you can make stateful traits, and apart from instantiation issues (see below), there is no need to distinguish a class from a trait anymore.
Yes, I see. It seems interesting approach. Since you have all needed (auxiliary) variables for a trait and do not pollute (as a mixin) object's state. I.e. the object may even not to have required methods/state for the trait? All this may be passed as arguments for a meta-trait?
Correct.
Ruby's modules and instance-class relationships indeed work via delegation and enable changes to all live instances by changing modules/classes, and per-instance customization. OTOH, because they chain together objects/classes/modules via delegation instead of flattening the properties, they don't provide the early conflict detection properties of traits. I think you can get either one or the other, but not both from 1 language feature. Javascript already has prototype delegation. Adding traits would allow us to express object composition in different ways, with different tradeoffs.
Yes, that exactly I was asking Mark. So the "issue" is that's not possible to have at the same time delegation base traits+protos and early conflict detection. I see. Don't know what is better. Such conflicts appear not often (and usually consciously are made by a programmer, i.e. "yes, I knew that previous mixin/trait also has such method, and I want to use the method from the second module. Therefore, I include first this module, and then -- the second one. If I wanted vice-versa, I'd included first the second module, and the the first one" -- that what is Ruby uses).
I agree, but I wish things were that simple. It all depends what your point of view is: from a software maintenance point of view, explicit conflict resolution (rather than implicit overriding) would be the better feature. I'm not denying that there is tradeoff here: traits are a more complex language feature than single inheritance, and don't cater to the 'live updating' feature of prototypal inheritance, but they support "safe" multiple inheritance and early conflict detection.
In MarkM's proposal, a "trait class" is a function that, when called, returns a property descriptor map. A "class" is a function that, when called, returns an object created from a property descriptor map. While technically you can "instantiate" a trait by calling a trait class function, you will end up with a property descriptor map, not an instance. From the end-user's point of view, it still only makes sense to instantiate class functions.
Yes, I see. And then this property descriptor (an instantiated trait) is merged with the original object.
OK, Tom, thanks for your clarifications, it was useful.
The idea of "non-traditional available for instantiation traits" sounds interesting. However, the issue with non-inheritable classes is have to avoided. I may ask any JS-programmer -- whether he needed classes in JS additionally to the (already available "classes" as "constructor+prototype" pair) prototypes, and moreover, these classes cannot inherit and every object created from a class will have own methods (but delegated ones) -- they will answer -- "No".
While I see your point, don't Javascript programmers also use alternatives to prototypal inheritance, such as "mixing in" properties of one object by copying them into other objects? Traits would provide more direct support for these use cases.
On 2010-09-15, at 14:44, Tom Van Cutsem wrote:
It's the easiest way though (Python e.g. uses linearization too to avoid diamonds issue). The way with warning a user about naming conflicts (in particular, in your implementation too) seems also good. From the other hand we don't warn a user if he overrides (in JS) a parent method with the same name.
Linearization is indeed a well-known approach to resolve the diamond, but it doesn't adequately 'solve' the problem from a software evolution point of view: small changes in one part of a large code base (i.e. changing the order of two 'include' or 'extends' statements) may end up impacting a totally different part of the code base - usually without warning. I can recommend Alan Snyder's classic 1986 OOPSLA paper titled "Encapsulation and Inheritance in Object-oriented Programming Languages", where he explains this and related issues with inheritance.
See also: en.wikipedia.org/wiki/C3_linearization and its antecedents.
On 15.09.2010 22:44, Tom Van Cutsem wrote:
Yeah, I see. Though, in ES it should be quite clear: there is one (vertical) prototype chain; at every link of the prototype chain, can be several included traits/mixins (i.e. horizontal chain of mixins). Thus, the last included module -- overrides (shadows) the previously included one. The same again is made in Ruby. At last, the whole inheritance chain (vertical + horizontal) is linear. I.e. possibly the diamond problem should not be solving in case of mixins/traits at all. Because traits/mixins are /exactly small traits/ (small additional part of functionality), which may be /completely independent by semantics/ from the the vertical chain. And therefore there is no need to know -- which method to call. The answer is simple (in case of mixins) -- the last one included module -- overrides previously included module in respect of the same method name (thus, in Ruby, own a class's method with the same name overrides module's one). It's the easiest way though (Python e.g. uses linearization too to avoid diamonds issue). The way with warning a user about naming conflicts (in particular, in your implementation too) seems also good. From the other hand we don't warn a user if he overrides (in JS) a parent method with the same name.
Linearization is indeed a well-known approach to resolve the diamond, but it doesn't adequately 'solve' the problem from a software evolution point of view: small changes in one part of a large code base (i.e. changing the order of two 'include' or 'extends' statements) may end up impacting a totally different part of the code base - usually without warning.
Yeah, but as I mentioned usually a user is aware about which modules he includes and know which consequences (just a shadowing) he will have in case of the same method names. In case if the user doesn't know with which modules he deals, yeah, it's good to inform him about that "be careful, the method of this trait shadows the previous one". By the way, as I also mentioned, it's not a big problem to have it (delegation based mixins with liniarization and naming conflicts avoiding) even implementing on ES3 -- gist.github.com/575982. There nothing prevents me to add the check at the beginning of the "mixing" function, whether any method from "module" is (in the inheritance + mixins chain) already in the "to" object. Though, this implementation is mostly about academic curiosity and has some lacks, however if to use new Proxy objects or even internal property [[traits]] to handle mixed traits chain on [[Get]], then it's easy possible to have the bundle "delegation based traits + naming conflicts avoiding" mechanism. At least at mixing stage (to catch the case when a new method is added to a trait, after the trait is already mixed, it's needed to have overridden e.g. [[DefineOwnProperty]] for traits).
I can recommend Alan Snyder's classic 1986 OOPSLA paper titled "Encapsulation and Inheritance in Object-oriented Programming Languages", where he explains this and related issues with inheritance.
Oh, thanks, I will (as also your paper about generative traits).
On the other hand, stateless traits have their drawbacks: you need to pollute a trait's public API with getter/setter methods to 'fake' state.
I though (again, maybe in "original traits"?) that all needed state for a trait is provided by a class (which includes the trait). And the trait in contrast with a mixin do not provide (and create during execution of its methods) any additional object's state, creating new instance properties. Or you mean some additional (auxiliary) variables needed for a trait?
Yes, you are right: original traits do not define instance properties. What I meant is that, if an original trait's provided methods depend on instance properties, then in principle the trait should require a getter/setter to manipulate those instance properties. This forces a class that uses an original trait to provide a (usually public) getter/setter method such that the trait can access the instance property. So actually I was incorrect: it's not the trait's public API that is polluted, but the class that uses trait's API (if the property was not intended to be visible to external clients in the first place).
The key idea behind the Traits discussed here and in that paper is that they are 'generative': you generate them by calling a function that returns new traits.
Yeah, sort of Meta-Traits (constructors of traits).
Indeed. Although I would stick with the more straightforward term 'trait generator'. The original Squeak Smalltalk traits implementation actually defined the concept of a MetaTrait (its role is to traits what metaclasses are to Smalltalk classes, but let's not go there ;-)
This allows these traits to capture lexically visible state (usually the arguments of the generator function). Using this approach you can make stateful traits, and apart from instantiation issues (see below), there is no need to distinguish a class from a trait anymore.
Yes, I see. It seems interesting approach. Since you have all needed (auxiliary) variables for a trait and do not pollute (as a mixin) object's state. I.e. the object may even not to have required methods/state for the trait? All this may be passed as arguments for a meta-trait?
Correct.
Ruby's modules and instance-class relationships indeed work via delegation and enable changes to all live instances by changing modules/classes, and per-instance customization. OTOH, because they chain together objects/classes/modules via delegation instead of flattening the properties, they don't provide the early conflict detection properties of traits. I think you can get either one or the other, but not both from 1 language feature. Javascript already has prototype delegation. Adding traits would allow us to express object composition in different ways, with different tradeoffs.
Yes, that exactly I was asking Mark. So the "issue" is that's not possible to have at the same time delegation base traits+protos and early conflict detection. I see. Don't know what is better. Such conflicts appear not often (and usually consciously are made by a programmer, i.e. "yes, I knew that previous mixin/trait also has such method, and I want to use the method from the second module. Therefore, I include first this module, and then -- the second one. If I wanted vice-versa, I'd included first the second module, and the the first one" -- that what is Ruby uses).
I agree, but I wish things were that simple. It all depends what your point of view is: from a software maintenance point of view, explicit conflict resolution (rather than implicit overriding) would be the better feature. I'm not denying that there is tradeoff here: traits are a more complex language feature than single inheritance, and don't cater to the 'live updating' feature of prototypal inheritance, but they support "safe" multiple inheritance and early conflict detection.
Yeah, I think so.
In MarkM's proposal, a "trait class" is a function that, when called, returns a property descriptor map. A "class" is a function that, when called, returns an object created from a property descriptor map. While technically you can "instantiate" a trait by calling a trait class function, you will end up with a property descriptor map, not an instance. From the end-user's point of view, it still only makes sense to instantiate class functions.
Yes, I see. And then this property descriptor (an instantiated trait) is merged with the original object. OK, Tom, thanks for your clarifications, it was useful. The idea of "non-traditional available for instantiation traits" sounds interesting. However, the issue with non-inheritable classes is have to avoided. I may ask any JS-programmer -- whether he needed classes in JS additionally to the (already available "classes" as "constructor+prototype" pair) prototypes, and moreover, these classes /cannot inherit/ and every object created from a class will have /own methods/ (but delegated ones) -- they will answer -- "No".
While I see your point, don't Javascript programmers also use alternatives to prototypal inheritance, such as "mixing in" properties of one object by copying them into other objects? Traits would provide more direct support for these use cases.
With the traits idea everything is clear for me now, it seems a good replacement for the current Prototype.js's "Object.extend" (which I again remind is borrowed from Ruby and there has a delegation-based nature). So here there are no questions.
But I said about useless /classes/ (where "useless" means: (1) non-inheritable and (2) again -- defining own properties but not delegation based). Though, the question should go to MarkM I guess. I've already heard some meanings "what? they provining classes? for what?". The answer is simple "for what?" -- just for a convenience sugar for /chaining prototypes/. Again, from this viewpoint, ECMAScript already has "classes" as a pair of "a Constructor function" + "a prototype chain". Because, Python's /"first-class" dynamic delegation based classes/ do not differ (much) from this ECMAScript's model. In Python classes are really just a /sugar/ -- a convenient way /just to chain these "prototypes"/. Exactly this sugar has e.g. again CoffeeScript
Up to now, the strawman page on "syntax for efficient traits" left unspecified the semantics of trait composition, deferring this to the traits.js library implementation. I thought I'd give a shot at specifying the semantics of trait composition independent of any concrete implementation: < strawman:traits_semantics>
Feedback is welcome. I'm sure there are parts that remain unclear and require further specification.
Cheers, Tom
2010/9/13 Mark S. Miller <erights at google.com>
Responding to Brendan's challenge at < esdiscuss/2010-September/011825>,
I've been working up < strawman:syntax_for_efficient_traits>.
Given the revival of that thread today, now seems like an opportune time to post it.
Seeing as how classes-as-sugar is getting a much cooler reception on the list than traits, if there are no objections, I propose to use the timeslot on the agenda currently reserved from classes-as-sugar to instead discuss traits and traits vs classes-as-sugar.
"Syntax for Efficient Traits" at < strawman:syntax_for_efficient_traits> is
now ready for discussion.