Please some small improvements
On Mar 20, 2012, at 2:10 PM, John J Barton wrote:
I've been reading a lot of nicely written JS in the Traceur compiler. They use a very conservative Java-like approach with Constructor functions and literal prototype property declarations. Sadly they must include workarounds for Object.create:
code.google.com/p/traceur-compiler/source/browse/src/traceur.js#80
WebInspector has a bit more rough and ready JS style, eg
WebInspector.View.prototype.proto = WebInspector.Object.prototype;
Here's the irony: we can change the WebInspector code to use Object.create() specifically because they do not use object literal declarations for functions (and thus one line will make improvement).
Since es-discuss seems quite keen on object literals, a couple of small improvements would help code move in your direction:
A version of Object.create() that takes an object RHS.
Because you explicitly mention object literals, I assume your intent would be to use an object literal as the second argument to such functions. For example such as is done in [1] that starts out as:
LoadCodeUnit.prototype = traceur.createObject(CodeUnit.prototype, { allowLoad: true,
get moduleSymbol() {
return this.project.getModuleForUrl(this.url)
},
...
In ES.next, based upon current proposals, the equivalent can be directly expressed using <| without any additional function calls:
LoadCodeUnit.prototype = CodeUnit.prototype <| { allowLoad: true,
get moduleSymbol() {
return this.project.getModuleForUrl(this.url)
},
...
In other words, <| is the operator forms of the function you are requesting for the object literal use case. Note that the operator form is likely to be significantly more efficient as it only needs to do one object allocation. The function form has to allocated the object literal that is passed as the argument to createObject and and then allocate a second object within createObject (and copy poperties between the two).
Object.merge() that merges the own non-function properties as own properties, the non-built-in-functions as prototype properties. (a compromise of the two common versions of extend, own and 'in' versions).
and what about own built-in function properties?
We have talked about providing some form of "extend". Do you have specific usage scenarios that support the specific set you rules you just listed.
On Tue, Mar 20, 2012 at 2:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Mar 20, 2012, at 2:10 PM, John J Barton wrote:
Object.merge() that merges the own non-function properties as own properties, the non-built-in-functions as prototype properties. (a compromise of the two common versions of extend, own and 'in' versions).
and what about own built-in function properties?
Sorry I don't understand what these are.
We have talked about providing some form of "extend".
I guess the talk never led to results in part because there are different versions of extend() in different JS code.
Do you have specific usage scenarios that support the specific set you rules you just listed.
Broadly this operation creates a new object by copying the properties from the argument objects.
So the dominant use cases are
- objects designed to be objects or
- objects designed to be [[Prototype]]s
- objects designed to be classes.
Merging all of the properties works fine for the 1, the object case.
I think the existing practice is designed for 2) [[Prototype]] case for use by developers who simply never mix piles of functions with piles of non-functions. They create .prototype objects by extend() on other .prototype objects. If you get some non-function properties on your [[Prototype]] chain then you can have two different classes of objects writing on each other. If this was not intended, the problem is very confusing and surprising.
For 3) you have to apply 1 and 2 to create the data and function properties separately.
So to my suggestion then: rather than a merge() or extend() function that treats all the uses cases uniformly, can we have one that addresses the key issues in object formation?
As the reader here know, languages with classes typically don't put data properties in the virtual function table. It's not called a virtual property table ;-). So the default for an operation to aid in the construction of class-like objects (2 and 3), should be to create an object with data-properties and a [[Prototype]] with functions. If you supply only functions, then you end up with something like Object.create() gives but with multiple inputs (a traits-like design).
By simply merging the data properties on the object and the functions onto the [[Prototype]] lots of use cases get covered. If you really wanted data properties on the [[Prototype]], don't use this feature. If you really want to control the [[Prototype]] chain, don't use this feature. Just the simple case.
Finally, compare our tools for objects to our tools for Arrays (forEach, et al). Maybe there is some way to solve this problem by combining some primitive operations.
jjb
On Tue, Mar 20, 2012 at 2:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Mar 20, 2012, at 2:10 PM, John J Barton wrote:
I've been reading a lot of nicely written JS in the Traceur compiler. They use a very conservative Java-like approach with Constructor functions and literal prototype property declarations. Sadly they must include workarounds for Object.create:
code.google.com/p/traceur-compiler/source/browse/src/traceur.js#80
WebInspector has a bit more rough and ready JS style, eg
WebInspector.View.prototype.proto = WebInspector.Object.prototype;
Here's the irony: we can change the WebInspector code to use Object.create() specifically because they do not use object literal declarations for functions (and thus one line will make improvement).
Since es-discuss seems quite keen on object literals, a couple of small improvements would help code move in your direction:
A version of Object.create() that takes an object RHS.
Because you explicitly mention object literals, I assume your intent would be to use an object literal as the second argument to such functions. For example such as is done in [1] that starts out as:
LoadCodeUnit.prototype = traceur.createObject(CodeUnit.prototype, { allowLoad: true,
get moduleSymbol() { return this.project.getModuleForUrl(this.url) }, ...
In ES.next, based upon current proposals, the equivalent can be directly expressed using <| without any additional function calls:
LoadCodeUnit.prototype = CodeUnit.prototype <| { allowLoad: true,
get moduleSymbol() { return this.project.getModuleForUrl(this.url) }, ...
In other words, <| is the operator forms of the function you are requesting for the object literal use case.
I agree that the <| would be a good match to the use shown in the Traceur code.
However, the example shows a significant pitfall waiting for the unsuspecting: data properties on the prototype. The allowLoad properties does appear to be intended as a 'static' or class value. In projects that never use object literals for data properties, then this ok. But many devs will use object literals for both functions and data. Soon or later we get burnt.
I suppose the <| operator has the advantage that linters can probably report data properties in the RHS at edit time. Making this a compile error should be considered.
jjb
On Mar 20, 2012, at 3:57 PM, John J Barton wrote:
On Tue, Mar 20, 2012 at 2:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Mar 20, 2012, at 2:10 PM, John J Barton wrote:
...
In ES.next, based upon current proposals, the equivalent can be directly expressed using <| without any additional function calls:
LoadCodeUnit.prototype = CodeUnit.prototype <| { allowLoad: true,
get moduleSymbol() { return this.project.getModuleForUrl(this.url) },
...
In other words, <| is the operator forms of the function you are requesting for the object literal use case.
I agree that the <| would be a good match to the use shown in the Traceur code.
However, the example shows a significant pitfall waiting for the unsuspecting: data properties on the prototype. The allowLoad properties does appear to be intended as a 'static' or class value. In projects that never use object literals for data properties, then this ok. But many devs will use object literals for both functions and data. Soon or later we get burnt.
Whether such a data property is good or bad, really depends upon the context. Maybe allowLoad:true is simply a default that is over-ridable on a per instance basis. In that case, what is wrong with putting it on the prototype? Would you be happier if it was defined as: get allowLoad() {return true} ?
If that is the same it makes it harder to over-ride for individual instances because they need to do a Object.defineProperty to install the over-ride rather than a simple assignment.
I suppose the <| operator has the advantage that linters can probably report data properties in the RHS at edit time. Making this a compile error should be considered.
But there are plenty of other uses of <| in conjunction with object literal data properties.
For example, you can use <| as an alternative to the new operator. Assume that you have the definition I quoted from Tracer then to create a LoadCodeUnit instance you might say:
let aCU = Load.CodeUnit <| {allowLoad: false, loader: myLoader, url: "foo.js", state: NOT_STARTED};
to create one that doesn't allow loading.
As another example, you can use prototype inheritance to buildup data records with shared parts. For example:
let wb = {lastName: "Wirfs-Brock"}; let wbOR = wb <| {state: "Oregon"}; let allenwb = wbOR <| {firstName: "Allen"}; let rjw = wbOR <| {firstName: "Rebecca"};
If you play nanny based upon only one usage pattern you preclude other uses. It's probably better to make such rules part of a project specific linter.
On Tue, Mar 20, 2012 at 4:34 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Mar 20, 2012, at 3:57 PM, John J Barton wrote:
On Tue, Mar 20, 2012 at 2:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Mar 20, 2012, at 2:10 PM, John J Barton wrote:
...
In ES.next, based upon current proposals, the equivalent can be directly expressed using <| without any additional function calls:
LoadCodeUnit.prototype = CodeUnit.prototype <| { allowLoad: true,
get moduleSymbol() { return this.project.getModuleForUrl(this.url) },
...
In other words, <| is the operator forms of the function you are requesting for the object literal use case.
I agree that the <| would be a good match to the use shown in the Traceur code.
However, the example shows a significant pitfall waiting for the unsuspecting: data properties on the prototype. The allowLoad properties does appear to be intended as a 'static' or class value. In projects that never use object literals for data properties, then this ok. But many devs will use object literals for both functions and data. Soon or later we get burnt.
Whether such a data property is good or bad, really depends upon the context.
Of course, but the design question is: Are the bad cases really bad and the good cases rare with simple alternatives? I say yes.
Maybe allowLoad:true is simply a default that is over-ridable on a per instance basis. In that case, what is wrong with putting it on the prototype? Would you be happier if it was defined as: get allowLoad() {return true} ?
That would be ok or setting the value in the constructor or LoadCodeUnit.prototype.allowLoad = true; Lots of good alternatives rather than playing with confusion and fire.
If that is the same it makes it harder to over-ride for individual instances because they need to do a Object.defineProperty to install the over-ride rather than a simple assignment.
Even this is ok by me. Make simple things simple; its ok if hard things are hard.
I suppose the <| operator has the advantage that linters can probably report data properties in the RHS at edit time. Making this a compile error should be considered.
But there are plenty of other uses of <| in conjunction with object literal data properties.
For example, you can use <| as an alternative to the new operator. Assume that you have the definition I quoted from Tracer then to create a LoadCodeUnit instance you might say:
let aCU = Load.CodeUnit <| {allowLoad: false, loader: myLoader, url: "foo.js", state: NOT_STARTED};
to create one that doesn't allow loading.
As another example, you can use prototype inheritance to buildup data records with shared parts. For example:
let wb = {lastName: "Wirfs-Brock"}; let wbOR = wb <| {state: "Oregon"}; let allenwb = wbOR <| {firstName: "Allen"}; let rjw = wbOR <| {firstName: "Rebecca"};
All cool examples.
If you play nanny based upon only one usage pattern you preclude other uses.
Sure but this very point is one that separates <| from 'class'. That is, Java et al preclude virtual data.
It's probably better to make such rules part of a project specific linter.
Or perhaps the check can be on the assignment to .prototype, the argument to Object.create(), or the RHS of <|.
jjb
The code sample uses allowLoad as the default value.
But, I do think that John's point is important. Putting mutable objects on the prototype chain is a foot gun. I don't believe in a linter being the right answer to this problem. The people that run into this problem are most likely not going to use a linter. This is why, if we ever agree on class syntax we might want to consider being opinionated and prevent such usage.
Erik Arvidsson wrote:
This is why, if we ever agree on class syntax we might want to consider being opinionated and prevent such usage.
I'll see and raise: class syntax should use a restrictive body plan that's not an object literal specifically so it can't have data properties where it has prototype methods and accessors. You might argue for an object literal with method definition shorthand and get/set accessors, and just ban data properties (p:42, e.g.) but:
-
People will expect them and we can't add them in the future without adding the footgun or diverging from object literals in how data properties bind instance properties, while methods and accessors bind prototype properties.
-
The comma separation is a pain, and unnecessary with bespoke class syntax, so usability says we should avoid it.
I’ve never seen it written quite this way. #1 is indeed the best argument against object literal (I thought we were calling them object initializers?) syntax for class declarations.
I wonder, though, if comma-separation can’t be learned, especially if trailing commas can be added (which is legal in ES5):
let obj = { foo: 123, method() { } bar: "abc", }
Manageable and the exact analog of semicolon separation.
Do you mean automatic comma insertion? Waldemar pointed out hazards involving [] and other combinations that use a lexeme that can be a prefix bracket form or a binary connective. On that basis I believe TC39 rejected comma elision as proposed for object literal (initialiser, sic, ECMA-262 says) syntax.
Do you mean automatic comma insertion? Waldemar pointed out hazards involving [] and other combinations that use a lexeme that can be a prefix bracket form or a binary connective. On that basis I believe TC39 rejected comma elision as proposed for object literal (initialiser, sic, ECMA-262 says) syntax.
No, just that, starting with ES5, you can write a trailing comma (like after bar
, below). If you combine that with the proposed shorter method syntax for object literals then syntactically, it’s just like replacing each semicolon with a comma, in a hypothetical semicolon-based syntax.
With "semicolon separation", I meant "semicolon-based syntax".
Confusion: then why did you leave a comma out (it's required in any proposed initialiser syntax) after method's closing brace?
As far as I can tell, there are no commas after concise methods.
I do not believe TC39 agreed to that.
On Mar 21, 2012, at 11:13 AM, Axel Rauschmayer wrote:
As far as I can tell, there are no commas after concise methods.
That was being floated for a while, but it's not how it is actually spec'ed in the ES6 draft. Commas are required.
OK, that indeed makes semicolons easier to handle.
I've been reading a lot of nicely written JS in the Traceur compiler. They use a very conservative Java-like approach with Constructor functions and literal prototype property declarations. Sadly they must include workarounds for Object.create:
code.google.com/p/traceur-compiler/source/browse/src/traceur.js#80
WebInspector has a bit more rough and ready JS style, eg
WebInspector.View.prototype.proto = WebInspector.Object.prototype;
Here's the irony: we can change the WebInspector code to use Object.create() specifically because they do not use object literal declarations for functions (and thus one line will make improvement).
Since es-discuss seems quite keen on object literals, a couple of small improvements would help code move in your direction:
A version of Object.create() that takes an object RHS.
Object.merge() that merges the own non-function properties as own properties, the non-built-in-functions as prototype properties. (a compromise of the two common versions of extend, own and 'in' versions).
jjb