strawman for the := operator
From the straw man:
class Point { constructor(x,y) { this := {x,y} //define and initialize x and y properties of new object } }
should this read:
this := {x: x, y: y}
?
This proposal offers a way to get around some of the strange semantics of '=', specifically the way read-only properties and setters on objects in the prototype chain can restrict what you can do on the receiver of an assignment. However it has some strangeness itself:
-
There is little point in having read-only properties if the common way to do assignment is :=. := will just walk all over a read-only property.
-
Copying private members from one object to another violates the encapsulation pretty badly. You would hope that using private names allowed you to easily reason about which objects have which properties, just by looking at the limited number of places a private name is used. But with this any code in the system that has two instances of a class can splat object a's private properties with those from object b. It's rather like a replay attack in crypto.
-
I don't understand the super stuff. Is there a typo here?: MyConstructor.prototype = Object.create(Baz); //inherit from Bar, not Foo
{x,Y} is syntactic shorthand for {x:x, y:y} See harmony:object_initialiser_shorthand
Thanks.
-
It has lot of typos (I remember two uses of := in examples in the first half where in fact legacy = should have been plus there are a few others, it would benefit from proofreading with someone with editing access).
-
As was already mentioned, the super rebinding allows me to just
iWantSuperHere := (class { m(...) {super....} }).prototype;
to get to generic super, but spec's lack of genericity must be worked around this way (or similar). Which is probably different discussion (and with this := around but with super still restricted to classes it would look strange or maybe even ridiculous).
On Aug 8, 2012, at 8:12 AM, Herby Vojčík wrote:
Thanks.
- It has lot of typos (I remember two uses of := in examples in the first half where in fact legacy = should have been plus there are a few others, it would benefit from proofreading with someone with editing access).
I fixed: let sealed := Object.seal(...
what's the other one?
On Mon, Aug 6, 2012 at 6:44 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Based upon discussions last week in the "July 25, 2012 - TC39 Meeting Notes" thread, I've created a new strawman proposal for a := operator. See strawman:define_properties_operator
:= is a convient way to copy properties from one object to another or to extend an object with new properties. It combines supports for many of the same use cases as the previously proposed "object extension literals" and the JSFixed Object.extend proposal.
The most important characteristic of := is that it uses [[DefineOwnProperty]] semantics rather than [[Put]] semantics to define properties on its target object so it doesn't run into issues with assignment to accessor properties or over-riding inherited readonly properties. It is also smart about dealing with methods that reference super.
Some basic examples:
target := src; //define all own properties of src onto target
//add a method and an accessor to an existing prototype Point.prototype := { plus(aPoint) {return new this.comstructor(this.x+aPoint.x,this.y+aPoint.y}, get rho() {return Math.sqrt(this.xthis.x+this.ythis.y} };
Have at it,
Allen,
Based on the semantics, specifically 4.I - 4.III, this doesn't work:
var div = document.createElement("div");
// Create a new div that will be hidden div := { innerHTML: "<p>P child element that contains a Text node</p>", id: "my-div", hidden: true };
...This is a pretty major use case that's satisfied by today's "extend" implementations.
I took a crack at implementing your semantics as "Object.extend" to feel out any issues or "gotchas" and came up with a few. I've posted the impl as well as a few relevant tests in this gist:
(NOTE: The super binding is probably wrong or inadequate)
I have a feeling that this may be overly complex, but I think that LeftHandSideExpression accessors should be enforced unless AssignmentExpression is defining new accessors of the same name. So, if the AssignmentExpression contains a data property of the same name as an existing property in the LeftHandSideExpression that is an accessor, the value of the AssignmentExpression's data property should be assigned, not defined.
I hope that makes sense.
On Wed, Aug 8, 2012 at 11:12 AM, Herby Vojčík <herby at mailbox.sk> wrote:
Thanks.
- It has lot of typos (I remember two uses of := in examples in the first half where in fact legacy = should have been plus there are a few others, it would benefit from proofreading with someone with editing access).
I saw and fixed this about an hour ago -
Erik Corry wrote:
Hi
This proposal offers a way to get around some of the strange semantics of '=', specifically the way read-only properties and setters on objects in the prototype chain can restrict what you can do on the receiver of an assignment.
This is "strange" only insofar as you can't say what you mean if you want to override. There's no law of nature requiring = to override, though.
Assigning != defining in ES5, even in reality in fixed implementations of ES3, and in truth going back to the first JS implementation: proto-setters (internal, hidden) are invoked by assignment, readonly proto-props prevent = being used to override on a delegating object (silent failure, of course, due to lack of try/catch).
What I'm getting at: is assignment != defining strange, or is the lack of expressiveness that left JS with = but not := strange, or is = not defining strange? I didn't want to assume the last was what you meant, since it is not obvious and not the only possible strangeness or asymmetry.
However it has some strangeness itself:
- There is little point in having read-only properties if the common way to do assignment is :=. := will just walk all over a read-only property.
No, readonly properties must be {writable: false, configurable: false} to have integrity, and := cannot redefine a non-configurable property.
- Copying private members from one object to another violates the encapsulation pretty badly. You would hope that using private names allowed you to easily reason about which objects have which properties, just by looking at the limited number of places a private name is used. But with this any code in the system that has two instances of a class can splat object a's private properties with those from object b. It's rather like a replay attack in crypto.
Yes, this is an open issue in my view. If one uses a private name to brand an object, attackers who have access to such an object can use := to forge trojan objects.
- I don't understand the super stuff.
Allen should field this one.
On Aug 8, 2012, at 9:32 AM, Rick Waldron wrote:
On Mon, Aug 6, 2012 at 6:44 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: Based upon discussions last week in the "July 25, 2012 - TC39 Meeting Notes" thread, I've created a new strawman proposal for a := operator. See strawman:define_properties_operator
:= is a convient way to copy properties from one object to another or to extend an object with new properties. It combines supports for many of the same use cases as the previously proposed "object extension literals" and the JSFixed Object.extend proposal.
The most important characteristic of := is that it uses [[DefineOwnProperty]] semantics rather than [[Put]] semantics to define properties on its target object so it doesn't run into issues with assignment to accessor properties or over-riding inherited readonly properties. It is also smart about dealing with methods that reference super.
Some basic examples:
target := src; //define all own properties of src onto target
//add a method and an accessor to an existing prototype Point.prototype := { plus(aPoint) {return new this.comstructor(this.x+aPoint.x,this.y+aPoint.y}, get rho() {return Math.sqrt(this.xthis.x+this.ythis.y} };
Have at it,
Allen,
Based on the semantics, specifically 4.I - 4.III, this doesn't work:
var div = document.createElement("div");
// Create a new div that will be hidden div := { innerHTML: "<p>P child element that contains a Text node</p>", id: "my-div", hidden: true };
...This is a pretty major use case that's satisfied by today's "extend" implementations.
The above is an assignment use case, not a definitional use case. For this you want something that uses [[Put]] semantics like strawman:batch_assignment_operator.
There really is a difference between [[Put]] and [[DefineOwnProperty]] semantics and when we tried to combine them in strawman:object_extension_literals it din't work very well.
A key point about := is that devs need to be intentional about whether they mean [[Put]] or [[DefineOwnProperty]]. They need to think about the differences and what they are actually trying to do with a specific piece of code. Part of the motivation of using two similar looking operators is to encourage devs to think "does this need to be a + or a := ?"
Currently, the convenience of = relative to the inconvenience of Object.defineProperty encourages devs to use = when they really intend [[DefineOwnProperty]] semantics. In some cases this does something other than was actually intended.
From your response, I infer we may also need to be concerned that the convenience of := for "setting" multiple property values may encourage dev to use := when they really intend =/[[Put]] semantics.
On Wed, Aug 8, 2012 at 1:08 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Aug 8, 2012, at 9:32 AM, Rick Waldron wrote:
On Mon, Aug 6, 2012 at 6:44 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Based upon discussions last week in the "July 25, 2012 - TC39 Meeting Notes" thread, I've created a new strawman proposal for a := operator. See strawman:define_properties_operator
:= is a convient way to copy properties from one object to another or to extend an object with new properties. It combines supports for many of the same use cases as the previously proposed "object extension literals" and the JSFixed Object.extend proposal.
The most important characteristic of := is that it uses [[DefineOwnProperty]] semantics rather than [[Put]] semantics to define properties on its target object so it doesn't run into issues with assignment to accessor properties or over-riding inherited readonly properties. It is also smart about dealing with methods that reference super.
Some basic examples:
target := src; //define all own properties of src onto target
//add a method and an accessor to an existing prototype Point.prototype := { plus(aPoint) {return new this.comstructor(this.x+aPoint.x,this.y+aPoint.y}, get rho() {return Math.sqrt(this.xthis.x+this.ythis.y} };
Have at it,
Allen,
Based on the semantics, specifically 4.I - 4.III, this doesn't work:
var div = document.createElement("div");
// Create a new div that will be hidden div := { innerHTML: "<p>P child element that contains a Text node</p>", id: "my-div", hidden: true };
...This is a pretty major use case that's satisfied by today's "extend" implementations.
The above is an assignment use case, not a definitional use case. For this you want something that uses [[Put]] semantics like strawman:batch_assignment_operator.
There really is a difference between [[Put]] and [[DefineOwnProperty]] semantics and when we tried to combine them in strawman:object_extension_literals it din't work very well.
I'm with you across the board here, frustrations of extension literals and all.
A key point about := is that devs need to be intentional about whether they mean [[Put]] or [[DefineOwnProperty]]. They need to think about the differences and what they are actually trying to do with a specific piece of code. Part of the motivation of using two similar looking operators is to encourage devs to think "does this need to be a + or a := ?"
Currently, the convenience of = relative to the inconvenience of Object.defineProperty encourages devs to use = when they really intend [[DefineOwnProperty]] semantics. In some cases this does something other than was actually intended.
From your response, I infer we may also need to be concerned that the convenience of := for "setting" multiple property values may encourage dev to use := when they really intend =/[[Put]] semantics.
Again, I'm with you in the desire to make [[Put]] and [[DefineOwnProperty]] semantics clearer while also providing a convenient syntax that further promotes the use of defined properties.
This all comes back to needing some form of "Object.extend" that behaves the way I explained above. To be clear, I want BOTH := and (not-yet-defined) "object extend"
snip
Again, I'm with you in the desire to make [[Put]] and [[DefineOwnProperty]] semantics clearer while also providing a convenient syntax that further promotes the use of defined properties.
This all comes back to needing some form of "Object.extend" that behaves the way I explained above. To be clear, I want BOTH := and (not-yet-defined) "object extend"
Specifically, I'd like to see:
Define Properties Operator strawman:define_properties_operator Batch Assignment Operator strawman:batch_assignment_operator (via dherman)
Maybe the "Batch Assignment Operator" should be renamed "Assign Properties Operator" to make it uniform and easier to understand its complementary nature with "Define Properties Operator"
On Wed, Aug 8, 2012 at 12:35 PM, Brendan Eich <brendan at mozilla.org> wrote:
Erik Corry wrote:
Hi
This proposal offers a way to get around some of the strange semantics of '=', specifically the way read-only properties and setters on objects in the prototype chain can restrict what you can do on the receiver of an assignment.
This is "strange" only insofar as you can't say what you mean if you want to override. There's no law of nature requiring = to override, though.
I actually think that the behavior of = makes total sense in to setters and read-only properties. If that was not the behavior, it would be pretty silly. If = on a setter overrode the setter, it would defeat the point of the setter. Same with read-only properties.
Assigning != defining in ES5, even in reality in fixed implementations of ES3, and in truth going back to the first JS implementation: proto-setters (internal, hidden) are invoked by assignment, readonly proto-props prevent = being used to override on a delegating object (silent failure, of course, due to lack of try/catch).
What I'm getting at: is assignment != defining strange, or is the lack of expressiveness that left JS with = but not := strange, or is = not defining strange? I didn't want to assume the last was what you meant, since it is not obvious and not the only possible strangeness or asymmetry.
I think the problem is really the conflation of = depending on the context. In more classical languages, there is a separation between methods and data. Overriding would never be done with =. If it was data, it would set the value of the field for that instance, and if it was defining a method, that would typically be done as an extension. I think that a happy path for users could aid in untangling the confusion, but I think we have to be clear about what that happy path should be.
However it has some strangeness itself:
- There is little point in having read-only properties if the common way to do assignment is :=. := will just walk all over a read-only property.
No, readonly properties must be {writable: false, configurable: false} to have integrity, and := cannot redefine a non-configurable property.
This brings me to an important point. I think we are conflating too much the ease of definition with the intent of Object.extend. Allen's strawman actually says := is similar in intent but not semantics. I don't like that, and I think it adds to the confusion. I think we have three cases here.
- You would like to mixin additional behaviors. This is commonly done with something like Object.extend, but that typically relies on [Put] semantics, but we really would like those to be defined. Mixins are available in many languages in various forms. There are many implementations for doing them in JavaScript and they are fairly commonly used. Mixins typically involve a group of methods which should always be mixed in together. Making super work correctly here is important.
- You would like to define a single property using a similar style as =, but you want to make sure it is a definition instead of a put.
- You have several values that you would like to [Put] - basically just a batch assignment.
I believe that := is ill suited to straightening out these cases. I think it will commonly be needed for #3, but instead do definition. Its convenience lends itself to abuse.
What I would recommend instead is that we match the cases more accurately. For #1, I think we try to frame it for what it is - a mixin. I think that is should be usable in conjunction with classes, as well single objects. I would probably suggest the keyword mixin, but since I think we're avoiding new keyword, perhaps we can reuse "with". Scala actually uses "with" for this exact purpose.
//can be used as part of the class declaration
class Foo with Enumerable {
//if similiar to ruby, would supply an each method here
}
//can be using in conjunction with extends
class Foo extends Bar with Enumerable{...}
//can also be used on a single instance
class Point {
constructor(x,y) {
this with {
//define accessor properties on new object that bind to
closure captured private state get x() {return x}, set x(value) {x = value), get y() {return y }, set y(value) {y = value}, moveTo(newX, newY} { x = newX; y = newY; } } } }
//assuming the 'with' operator returned the LHS after the operation, it
would be easy to be expressive doing something like let myPoint = new Point() with OneOffMixin;
One important point to be clear about. Even though the RHS would allow any object, it would only actually copy the functions. This would be consistent with MaxMin classes, and prevent the foot gun that Rick points out in his dom example.
For case #2, I think that if we still want an easier way to do defineProperty, we can still have :=, but we keep it similar to =.
From the strawman problem example, that means you would have the choice -
instead of =, you can do :=, but its one to one.
//you can choose to do this, which is a [Put]
obj.c = -3;
//or this which is a [DefineOwnProperty]
obj.c := -3;
For case #3, we might want to revisit .= or .{ where they are always [Put] based. I think that with the other constructs, it will be a lot more obvious what the intention is. Or potentially, we just do Object.extend (although I would recommend a different name because I think its confusing in relation to the extends keyword of classes).
Define Properties Operator strawman:define_properties_operator Batch Assignment Operator strawman:batch_assignment_operator (via dherman)
It's not clear to me that a batch assignment operator would obviate the need for similar library functions in the wild. Sometimes one wants to overwrite the properties of the target object, but other times one only wants to set a value when the target object does not already have a property defined for that name (as in underscore's "defaults").
A batch-assignment operator in this way is similar to a flat-head screwdriver. Nice to have, but you're still going to need to go to the hardware store to assemble that dresser from Ikea.
Kevin Smith wrote:
Define Properties Operator http://wiki.ecmascript.org/doku.php?id=strawman:define_properties_operator Batch Assignment Operator http://wiki.ecmascript.org/doku.php?id=strawman:batch_assignment_operator (via dherman)
It's not clear to me that a batch assignment operator would obviate the need for similar library functions in the wild. Sometimes one wants to overwrite the properties of the target object, but other times one only wants to set a value when the target object does not already have a property defined for that name (as in underscore's "defaults").
A batch-assignment operator in this way is similar to a flat-head screwdriver. Nice to have, but you're still going to need to go to the hardware store to assemble that dresser from Ikea.
Nicely put.
The other advantage of an Object.extend or Object.update function is that developers can polyfill. Yes, Object.extend in JS libraries relies on for-in -- but Object.prototype is verboten, so that's mostly ok by convention.
The point is not that we should standardize Object.extend self-hosted via for-in, as in PrototypeJS. Rather, we would maximize developer utility and avoid the need for ES6->ES5 compilers if we were to provide
an Object.update or Object.assign (let's call it) that copies own properties of its second argument into its first, shallow copying as if by assignment ([[Put]], not [[DefineOwnProperty]]).
This [[Put]] not [[DefineOwnProperty]] argues for the Object.assign name. The "assign" trope argues against processing 3rd, etc. actual arguments like the 2nd (as some library variants of Object.extend do).
On Aug 8, 2012, at 9:35 AM, Brendan Eich wrote:
Erik Corry wrote:
Hi
This proposal offers a way to get around some of the strange semantics of '=', specifically the way read-only properties and setters on objects in the prototype chain can restrict what you can do on the receiver of an assignment.
This is "strange" only insofar as you can't say what you mean if you want to override. There's no law of nature requiring = to override, though.
Assigning != defining in ES5, even in reality in fixed implementations of ES3, and in truth going back to the first JS implementation: proto-setters (internal, hidden) are invoked by assignment, readonly proto-props prevent = being used to override on a delegating object (silent failure, of course, due to lack of try/catch).
What I'm getting at: is assignment != defining strange, or is the lack of expressiveness that left JS with = but not := strange, or is = not defining strange? I didn't want to assume the last was what you meant, since it is not obvious and not the only possible strangeness or asymmetry.
However it has some strangeness itself:
- There is little point in having read-only properties if the common way to do assignment is :=. := will just walk all over a read-only property.
No, readonly properties must be {writable: false, configurable: false} to have integrity, and := cannot redefine a non-configurable property.
And, := should not be thought of or become the common way to do assignment. That should remain the job of =
- Copying private members from one object to another violates the encapsulation pretty badly. You would hope that using private names allowed you to easily reason about which objects have which properties, just by looking at the limited number of places a private name is used. But with this any code in the system that has two instances of a class can splat object a's private properties with those from object b. It's rather like a replay attack in crypto.
Yes, this is an open issue in my view. If one uses a private name to brand an object, attackers who have access to such an object can use := to forge trojan objects.
"Encapsulation"? ES doesn't have any encapsulation...
I originally thought that I would spec. := to ignore properties with private names (but not those with just unique names). As I thought about this more, I realized that in many cases this was likely to violate the user's intent when using :=.
Here is a trivial example, that is only intended to be illustrative of more likely scenarios:
//this would all be modularized in some manner const px = Name.private(), py = Name.private(); //I'm not sure what this API is ultimately going to look like let PointLike = { get x() {return this[px]}, set x(v) {this[px] = v}, get y() {return this[py]}, set y(v) {this[py] = v} }; Object.defineProperty(PointLike,px,{value: writable: true}); //@px: 0 in the above obj lit would be so much nicer.. Object.defineProperty(PointLike,py,{value: 0, writable: true});
//somewhere else in program:
myObj := PointLike; // mixin the PointLike properties
Presumably, the above mixinobject would advertise that it provides public 'x' and 'y' properties, but it doesn't advertise or otherwise reveal the private names of the px and py properties.
If := does not replicate the private named properties that the public properties are dependent upon then the mixin won't work. Essentially, the public and private named properties form a cohesive unit that needs to be replicated as a unit. But this don't violate the secretness of those private names. As long as the original definer of the names has protected them as a secret then only the public methods they provide that know the secret names can actually reference those properties.
This does mean that the simple existence of some specific private named property is insufficient to serve as a reliable brand (for some meaning of brand). Such brand properties need to be own properties, otherwise proto tweaking could be used to circumvent such branding. So, there is a simple workaround. When defining a own brand property set the value of the property to the object. Then a brand check would look like:
function isGenuine(obj) {return this[mySecretBrand] === this}
Cloning the mySecretBrand property of an object breaks the branding. This, or similar identify based techniques could also be a solution to the "like a replay attach" issue.
Finally, I do have a way that we could put the cloning of private named properties by := under programmer control. Note that private named properties are never enumerated or exposed via reflection. Hence, the [[Enumerable]] attribute really doesn't mean anything for such properties. We could enhance the semantics of := so that used the value of the [[Enumerable]] attribute to control the whether or not private named (and only private named) properties are cloned. I'm not thrilled with this idea, but it probably could work.
Russell Leggett wrote:
On Wed, Aug 8, 2012 at 12:35 PM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:
Erik Corry wrote: Hi This proposal offers a way to get around some of the strange semantics of '=', specifically the way read-only properties and setters on objects in the prototype chain can restrict what you can do on the receiver of an assignment. This is "strange" only insofar as you can't say what you mean if you want to override. There's no law of nature requiring = to override, though.
I actually think that the behavior of = makes total sense in
to setters and read-only properties. If that was not the behavior, it would be pretty silly. If = on a setter overrode the setter, it would defeat the point of the setter. Same with read-only properties.
To be fair, the issue is not overridng "own" read-only or setter-based properties. It is overriding delegated properties.
But there too one can argue, and extant code wants (see WebIDL), assignment-invoking-a-setter to abstract over prototype delegation. That's enough for me to want non-writable assignment failure to match. Others disagree, but I think they're doing so out of pragmatism more than principle (the Caja/SES frozen built-in prototypes problem).
Pragmatism is fine, but for the long run I think we are better off providing both assign and define "screwdrivers" (to use Kevin Smith's metaphor). Then in principle everyone wins. There's still a hard case for Caja on older browsers, but that's (a) temporary; (b) solvable at some cost (as Mark et al. cleverly have indeed solved it).
So I think the long-run right answer, given the sunk cost non-fallacy of Caja having to deal with most browsers in the field rejecting write attempts that would shadow a non-writable proto-property, is to level the field: = and :=, Object.assign and Object.define if appropriate. I'm not sure batch assignment pays for itself, but we can check on Dart's experience and user-test and argue that one separately.
Assigning != defining in ES5, even in reality in fixed implementations of ES3, and in truth going back to the first JS implementation: proto-setters (internal, hidden) are invoked by assignment, readonly proto-props prevent = being used to override on a delegating object (silent failure, of course, due to lack of try/catch). What I'm getting at: is assignment != defining strange, or is the lack of expressiveness that left JS with = but not := strange, or is = not defining strange? I didn't want to assume the last was what you meant, since it is not obvious and not the only possible strangeness or asymmetry.
I think the problem is really the conflation of = depending on the context. In more classical languages, there is a separation between methods and data. Overriding would never be done with =. If it was data, it would set the value of the field for that instance, and if it was defining a method, that would typically be done as an extension. I think that a happy path for users could aid in untangling the confusion, but I think we have to be clear about what that happy path should be.
It's hard to disagree :-).
However it has some strangeness itself: * There is little point in having read-only properties if the common way to do assignment is :=. := will just walk all over a read-only property. No, readonly properties must be {writable: false, configurable: false} to have integrity, and := cannot redefine a non-configurable property.
This brings me to an important point. I think we are conflating too much the ease of definition with the intent of Object.extend. Allen's strawman actually says := is similar in intent but not semantics. I don't like that, and I think it adds to the confusion. I think we have three cases here.
Oh, I agree completely if the topic is Object.extend -- which is a well-know library method that uses assignment, not any ES5-only Object.defineProperty API.
- You would like to mixin additional behaviors. This is commonly done with something like Object.extend, but that typically relies on [Put] semantics, but we really would like those to be defined. Mixins are available in many languages in various forms. There are many implementations for doing them in JavaScript and they are fairly commonly used. Mixins typically involve a group of methods which should always be mixed in together. Making super work correctly here is important.
Agreed, mixins are a use-case to serve, not necessarily by anything we are currently discussing.
I disagree that Object.extend does "mixins" if the methods should always be mixed in together. It may be that when using PrototypeJS in the past, you could get away with using Object.extend this way. With ES5 you can't. See traitsjs.org for an ES5-based approach that enforces all-or-none-with-throw and other mixin properties.
Given the ability to specify non-configurability along with non-writability in ES5, := can be used for high-integrity mixins.
- You would like to define a single property using a similar style as =, but you want to make sure it is a definition instead of a put.
Object.defineProperty, but it's verbose and := seems a strict improvement to me.
- You have several values that you would like to [Put] - basically just a batch assignment.
I believe that := is ill suited to straightening out these cases.
See above. I think your (1) is ill-defined, but for high-integrity cases you seem to want ("should always be mixed in together"), ES5 and any sugar such as := do help. For (2), := helps because there's no shorter way to specify attributes.
I think it will commonly be needed for #3, but instead do definition. Its convenience lends itself to abuse.
I doubt this. Arguments of the "developers will use it wrong" need evidence, and developers are smart. They learn.
On 8/8/2012 11:40 AM, Brendan Eich wrote:
To be fair, the issue is not overridng "own" read-only or setter-based properties. It is overriding delegated properties.
But there too one can argue, and extant code wants (see WebIDL), assignment-invoking-a-setter to abstract over prototype delegation. That's enough for me to want non-writable assignment failure to match. Others disagree, but I think they're doing so out of pragmatism more than principle (the Caja/SES frozen built-in prototypes problem).
Pragmatism is fine, but for the long run I think we are better off providing both assign and define "screwdrivers" (to use Kevin Smith's metaphor). Then in principle everyone wins. There's still a hard case for Caja on older browsers, but that's (a) temporary; (b) solvable at some cost (as Mark et al. cleverly have indeed solved it).
So I think the long-run right answer, given the sunk cost non-fallacy of Caja having to deal with most browsers in the field rejecting write attempts that would shadow a non-writable proto-property, is to level the field: = and :=, Object.assign and Object.define if appropriate. I'm not sure batch assignment pays for itself, but we can check on Dart's experience and user-test and argue that one separately.
I am not in favor of adding := to the language (as I am not in favor of adding to the language generally), but if := is added, my practical advice will be to always use := and never use =. We will probably see better performance with := since it doesn't have to slide down the proto chain to see if the assignment works. And I like stepping away from ='s visual ambiguity, looking too much like an equality operator. It could be argued that = is still needed for respecting some kinds of inherited stuff, but it could also be argued that those kinds of inherited stuff are just weird edge cases that can now be conveniently and efficiently avoided.
So if we do add :=, we should allow it to work on variables too, for consistency.
Finally, I do have a way that we could put the cloning of private named properties by := under programmer control.
Giving objects control over how they appear to := and other Object.extend variants sounds like a good idea.
Note that private named properties are never enumerated or exposed via reflection. Hence, the [[Enumerable]] attribute really doesn't mean anything for such properties. We could enhance the semantics of := so that used the value of the [[Enumerable]] attribute to control the whether or not private named (and only private named) properties are cloned. I'm not thrilled with this idea, but it probably could work.
This seems to abuse a single property to control two different internal iterators: for-in and :=. It is in the nature of for-in that it exposes the enumerated properties, while := just copies them over (if you don't have the internal private names, you still cannot use the private properties directly).
Perhaps this situation calls for a separate iterator, now that iterators can be defined in ES? A cloning iterator, inherited from Object.prototype (for convenience), overridable as needed.
If private properties can be cloned, cloning an object with private properties would differ from cloning an object with variables in its closure. Not being able to break sharing of closed-over variables (emulated private state) has been an issue for user-defined clone operations.
If := does not replicate the private named properties that the public properties are dependent upon then the mixin won't work. Essentially, the public and private named properties form a cohesive unit that needs to be replicated as a unit.
That calls for a way to abort (or rather, not implement) the := iterator, for objects whose properties cannot be sensibly be copied this way.
Claus
Douglas Crockford wrote:
I am not in favor of adding := to the language (as I am not in favor of adding to the language generally), but if := is added, my practical advice will be to always use := and never use =.
I hesitate to point this out, since it will convince you that := should not be added, but := is not a substitute for =!
As proposed at strawman:define_properties_operator by Allen, both the LHS and RHS of := are ToObject-converted (only null and undefined throw) and the result of so converting the RHS is inspected for own properties which are copied into the LHS as if by ES5 meta-object APIs that preserve accessors (vs. data props), attributes, etc.
So you can't change x = 42 into x := 42. First, x must not denote null or undefined. Second, ToObject(42) has no own properties so nothing new is defined on x. Third, if x were a primitive boolean, number, or string, I take it from Allen's proposal that a throwaway Boolean, Number, or String wrapper would be created and (if the RHS dictates) extended.
We will probably see better performance with := since it doesn't have to slide down the proto chain to see if the assignment works.
This isn't a problem in at least some modern JITs.
And I like stepping away from ='s visual ambiguity, looking too much like an equality operator. It could be argued that = is still needed for respecting some kinds of inherited stuff, but it could also be argued that those kinds of inherited stuff are just weird edge cases that can now be conveniently and efficiently avoided.
You mean every single DOM property specified by a WebIDL attribute?
So if we do add :=, we should allow it to work on variables too, for consistency.
See above -- := is not a drop-in replacement for =. Think of it more like the .{ proposal, which had at one point suggested alternative syntax: .= (with an object literal to the right, but the latest proposal relaxes that to be any expression given to ToObject).
On Aug 8, 2012, at 11:23 AM, Brendan Eich wrote:
The other advantage of an Object.extend or Object.update function is that developers can polyfill. Yes, Object.extend in JS libraries relies on for-in -- but Object.prototype is verboten, so that's mostly ok by convention.
The point is not that we should standardize Object.extend self-hosted via for-in, as in PrototypeJS. Rather, we would maximize developer utility and avoid the need for ES6->ES5 compilers if we were to provide an Object.update or Object.assign (let's call it) that copies own properties of its second argument into its first, shallow copying as if by assignment ([[Put]], not [[DefineOwnProperty]]).
I'm not yet convinced that Object.update should be restricted to own properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited.
This [[Put]] not [[DefineOwnProperty]] argues for the Object.assign name. The "assign" trope argues against processing 3rd, etc. actual arguments like the 2nd (as some library variants of Object.extend do).
Object.assign sounds pretty good. But I do like Object.update. Somehow the latter seems more evocative of what the programmer's trying to accomplish, rather than naming it in terms of a language mechanism.
I'm not yet convinced that Object.update should be restricted to own properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited.
Can you elaborate? Because usually, you have instances that point to shared prototypes. Then you’d also want only the own properties. If there is a type hierarchy, all constructors put all instance data in the same object, so you already have an automatic flattening.
On Aug 8, 2012, at 3:08 PM, Axel Rauschmayer wrote:
I'm not yet convinced that Object.update should be restricted to own properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited.
Can you elaborate? Because usually, you have instances that point to shared prototypes. Then you’d also want only the own properties. If there is a type hierarchy, all constructors put all instance data in the same object, so you already have an automatic flattening.
That's a fair point. I have found that I will occasionally have a type-level "constant" that makes sense to keep as an inherited data property, but I'll grant it's somewhat rare.
Still, the fact that it works fine without the own-check today:
http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.extend
indicates that code is working fine today without prototype pollution problems (especially given that basically everything on the web breaks if you don't respect "Object.prototype is verboten").
So before going with the own-check, I'd want to know that existing patterns wouldn't break, and whether it really would help that much.
On Aug 8, 2012, at 2:23 PM, Brendan Eich wrote:
So if we do add :=, we should allow it to work on variables too, for consistency.
See above -- := is not a drop-in replacement for =. Think of it more like the .{ proposal...
With too many of these conversations about various "modify an object" operators, we've started from "here's a thing let's talk about it" rather than "here's the problem we're trying to solve." Specifically, for there to be dedicated syntax, the bar is high to prove that it pays for itself.
Every time we talk about an operator whose semantics is based off of defineProperty, I'm skeptical that it's providing real programmer benefit, as opposed to sort of "completing the set" of semantic operations with dedicated syntax. Object.defineProperty is a fine thing to have, and I'm glad ES5 added it, but IMO it's a lower-level reflection operation, not a common operation. Allen should correct me, but I think he doesn't disagree, since he says:
And, := should not be thought of or become the common way to do assignment. That should remain the job of =
If it's not common, then that's a strong case against getting dedicated syntax.
I'm not yet convinced that Object.update should be restricted to own properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited.
Can you elaborate? Because usually, you have instances that point to shared prototypes. Then you’d also want only the own properties. If there is a type hierarchy, all constructors put all instance data in the same object, so you already have an automatic flattening.
That's a fair point. I have found that I will occasionally have a type-level "constant" that makes sense to keep as an inherited data property, but I'll grant it's somewhat rare.
Still, the fact that it works fine without the own-check today:
james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.extend
indicates that code is working fine today without prototype pollution problems (especially given that basically everything on the web breaks if you don't respect "Object.prototype is verboten").
So before going with the own-check, I'd want to know that existing patterns wouldn't break, and whether it really would help that much.
I can see copying more than the own properties as potentially useful. But then the question is whether one wouldn’t want to preserve the structure. That is, if it was an option, it would be: How long a prefix of the prototype chain to clone? 1 would be the default, 0 or -1 could be used for: “the complete prototype chain”.
Loosely related: I recently looked at _.extend and was surprised that it didn’t copy non-enumerable properties (as far as I can tell, impossible pre ES5) and that it included all inherited properties. OTOH, if you include non-enumerable properties, it becomes impossible to include all inherited properties by default.
David Herman wrote:
On Aug 8, 2012, at 3:08 PM, Axel Rauschmayer wrote:
I'm not yet convinced that Object.update should be restricted to own properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited. Can you elaborate? Because usually, you have instances that point to shared prototypes. Then you’d also want only the own properties. If there is a type hierarchy, all constructors put all instance data in the same object, so you already have an automatic flattening.
That's a fair point. I have found that I will occasionally have a type-level "constant" that makes sense to keep as an inherited data property, but I'll grant it's somewhat rare.
It's an anti-pattern, in general: constants go on constructors or in "package" objects. Too many colllision and deep-mutable hazards on the proto-chain for advisory constants.
Still, the fact that it works fine without the own-check today:
http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.extend
indicates that code is working fine today without prototype pollution problems (especially given that basically everything on the web breaks if you don't respect "Object.prototype is verboten").
I made the same argument but reached a different conclusion: proto-chain walking not wanted for Object.extend, because if people did extend Object.prototype, lots of for-in uses, especially including the one in Prototype's Object.extend, would break.
So before going with the own-check, I'd want to know that existing patterns wouldn't break, and whether it really would help that much.
This is not a case of us knowing too little, or having a neutral stance in the face of a balanced controversy. Object.prototype is verboten because for-in walks the proto chain, and it's used by Object.extend and other such things. Plus, people don't generally compose via proto (Object.create is new) or flatten proto-chains intentionally.
David Herman wrote:
On Aug 8, 2012, at 2:23 PM, Brendan Eich wrote:
So if we do add :=, we should allow it to work on variables too, for consistency. See above -- := is not a drop-in replacement for =. Think of it more like the .{ proposal...
(Note: I did write the double-cited text above, in reply to Doug.)
With too many of these conversations about various "modify an object" operators, we've started from "here's a thing let's talk about it" rather than "here's the problem we're trying to solve."
I don't think that's fair. We have been talking about Object.extend, the possible value of syntax for it, assign vs. define, all based on real use-cases. The Caja experience shows that people do use = to shadow prototype properties, when that can't work if the proto-prop is accessor not data (this gives Caja its workaround). We're pretty real-problem-based on this stuff.
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
:= is only the latest in a line of proposals for solutions.
Specifically, for there to be dedicated syntax, the bar is high to prove that it pays for itself.
Agreed.
Every time we talk about an operator whose semantics is based off of defineProperty, I'm skeptical that it's providing real programmer benefit, as opposed to sort of "completing the set" of semantic operations with dedicated syntax. Object.defineProperty is a fine thing to have, and I'm glad ES5 added it, but IMO it's a lower-level reflection operation, not a common operation. Allen should correct me, but I think he doesn't disagree, since he says:
And, := should not be thought of or become the common way to do assignment. That should remain the job of =
(That double-cited text is not mine, however -- it is from Allen, in reply to me to Doug -- but Allen is not on your To/cc list for some reason...)
If it's not common, then that's a strong case against getting dedicated syntax.
That is a bit circular. I agree the bar should be high, but the need for := is masked by overuse of =, which is buggy as the Caja experience showed.
The deeper point: maybe it's too late to fix anything (for Caja, or on balance). But Allen argues the future of JS is longer than its past.
On 8/8/2012 5:01 PM, Brendan Eich wrote:
:= is only the latest in a line of proposals for solutions.
Please excuse my confusion. For some reason I expected := to be like an assignment operator.
On Wed, Aug 8, 2012 at 4:52 PM, Brendan Eich <brendan at mozilla.org> wrote:
David Herman wrote:
On Aug 8, 2012, at 3:08 PM, Axel Rauschmayer wrote:
I'm not yet convinced that Object.update should be restricted to own
properties. If you're only using object literals, then yeah, you want own properties only; you're basically using object literals as a poor man's named arguments. But if you're just taking an arbitrary object, there's less reason to expect that its relevant properties won't be inherited.
Can you elaborate? Because usually, you have instances that point to shared prototypes. Then you’d also want only the own properties. If there is a type hierarchy, all constructors put all instance data in the same object, so you already have an automatic flattening.
That's a fair point. I have found that I will occasionally have a type-level "constant" that makes sense to keep as an inherited data property, but I'll grant it's somewhat rare.
It's an anti-pattern, in general: constants go on constructors or in "package" objects. Too many colllision and deep-mutable hazards on the proto-chain for advisory constants.
Still, the fact that it works fine without the own-check today:
http://james.padolsey.com/**jquery/#v=1.7.2&fn=jQuery.**extend<http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.extend>
indicates that code is working fine today without prototype pollution problems (especially given that basically everything on the web breaks if you don't respect "Object.prototype is verboten").
I made the same argument but reached a different conclusion: proto-chain walking not wanted for Object.extend, because if people did extend Object.prototype, lots of for-in uses, especially including the one in Prototype's Object.extend, would break.
Perhaps the current practice is not a great resource for making decisions here. I'm not up on jQuery, but the most common pattern I've seen for extend()-ish methods is to apply them to .prototype properties, with two unspoken and unchecked constraints: only add functions to .prototype objects and don't dynamically modify base prototypes to affect derived ones. That is, the extend() is used to help implement classical inheritance. In this case, the difference between own and chain copies is minor performance issues. Perhaps that explains both the variation among extend() implementations and the lack of own-checks: in practice it's not so important either way.
jjb
On Aug 8, 2012, at 5:01 PM, Brendan Eich wrote:
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
OK, that's the real problem, thanks for making it clear -- sorry if I missed it before.
Allen should correct me, but I think he doesn't disagree, since he says:
And, := should not be thought of or become the common way to do assignment. That should remain the job of =
(That double-cited text is not mine, however -- it is from Allen, in reply to me to Doug -- but Allen is not on your To/cc list for some reason...)
Yes, I was purposefully quoting Allen (note my words, "Allen should correct me... he says:") from another message via copy/paste-quoted. I try to avoid needless proliferation of messages when I can condense into a single reply.
If it's not common, then that's a strong case against getting dedicated syntax.
That is a bit circular.
I was not saying "if it's not already common then we shouldn't support it" -- you know me, that's not an argument I would make. Allen was saying it should not become the common way to do assignment. So then the question becomes: how common should it be, then? Are the use cases it addresses common enough to warrant new syntax?
You've clarified that this is about working around the problem of non-overridable prototype properties. That's a real issue, I agree, but I'm not convinced it's general enough to need syntax.
The deeper point: maybe it's too late to fix anything (for Caja, or on balance). But Allen argues the future of JS is longer than its past.
Again, I'm not arguing the pave-nothing-but-cowpaths argument. I'm simply arguing that syntax needs a better justification than whether it solves a problem; it needs to fit in as a comfortable, central idiom. Especially syntax that looks like a new variation on an existing thing. ("Wait, which kind of assignment do I need to use here?") But I need to study the strawman more.
No problem. For anyone used to Pascal or Ada, or familiar with Go, := could indeed be a speedbump.
We could fall back on .= but that loses the :, which Allen points out reminds users of object literals, which (in reality as a fix to ES3, and officially in ES5) uses [[DefineOwnProperty]] not [[Put]].
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
OK, that's the real problem, thanks for making it clear -- sorry if I missed it before.
I still find the difference between assigning and defining very subtle. I am still trying to fully wrap my head around it.
Isn’t there a reverse danger, too? That people use define when they actually want to use assignment? They might, for example, use := and expect a setter to be called.
What is the use case for overriding? Isn’t it usually when you copy one object to another (a.k.a. Object.update() or Object.assign())? There you see code such as:
documentcloud/underscore/blob/master/underscore.js#L690
// Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { each(slice.call(arguments, 1), function(source) { for (var prop in source) { obj[prop] = source[prop]; } }); return obj; };
Clearly, here it is wrong to use assignment, but one has not choice if one wants to be compatible with ES3.
Isn’t the answer Object.update(), then? Or are there other common pitfalls?
On Wed, Aug 8, 2012 at 9:57 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
OK, that's the real problem, thanks for making it clear -- sorry if I missed it before.
I still find the difference between assigning and defining very subtle. I am still trying to fully wrap my head around it.
The best way to think about it is to think of how innerHTML works. If you "define" a property called "innerHTML", it would pave over the existing innerHTML and all of the semantics would be lost. When you "assign" to elem.innerHTML, the accessor semantics handle updating the DOM with the value given as the AssignmentExpression on RHS.
Hopefully that helps?
I still find the difference between assigning and defining very subtle. I am still trying to fully wrap my head around it.
The best way to think about it is to think of how innerHTML works. If you "define" a property called "innerHTML", it would pave over the existing innerHTML and all of the semantics would be lost. When you "assign" to elem.innerHTML, the accessor semantics handle updating the DOM with the value given as the AssignmentExpression on RHS.
Hopefully that helps?
Thanks. With properties, we are missing the clarity that we have with variables: You can’t assign to an undeclared variable (in strict mode). With properties, assignment “auto-declares” if there is no own property and no setter. So things are less clear.
David Herman wrote:
On Aug 8, 2012, at 5:01 PM, Brendan Eich wrote:
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
OK, that's the real problem, thanks for making it clear -- sorry if I missed it before.
No problem. The "succinctly and reliably" conjunction is important, but it's not an argument for new syntax, by itself. I think we agree that an API such as Object.update would solve most of the problem, if not all ("succinctly"). Object.update could be polyfilled, to boot.
So then the question becomes: how common should it be, then? Are the use cases it addresses common enough to warrant new syntax?
It's hard to say for sure, but I find the class "statics" use-case compelling, since maximin classes don't have any declarative support for class-side properties.
Especially syntax that looks like a new variation on an existing thing. ("Wait, which kind of assignment do I need to use here?")
That does look like trouble, now that you and Doug point it out. We have spiraled around .{ and := but not .= -- would .= be better?
On Aug 8, 2012, at 9:36 PM, Axel Rauschmayer wrote:
I still find the difference between assigning and defining very subtle. I am still trying to fully wrap my head around it.
The best way to think about it is to think of how innerHTML works. If you "define" a property called "innerHTML", it would pave over the existing innerHTML and all of the semantics would be lost. When you "assign" to elem.innerHTML, the accessor semantics handle updating the DOM with the value given as the AssignmentExpression on RHS.
Hopefully that helps?
Thanks. With properties, we are missing the clarity that we have with variables: You can’t assign to an undeclared variable (in strict mode). With properties, assignment “auto-declares” if there is no own property and no setter. So things are less clear.
I think this lack of clarity may be a symptom of thinking about JS programming tasks at too low of a level.
Here is how I would make the distinction:
In JavaScript we deal with abstractions that are represented as objects. These abstractions are distinguished by the properties they expose and their associated behaviors.
In some parts of a program you are defining new abstractions (either by creating new abstractions from scratch or by extending existing abstractions). As you are designing such abstractions you think about possibilities. What role does this abstraction serve in the larger context of my program. How will its consumers interact with it. What do I need to define to enable those possibilities.
In other parts of a program you are interacting with concrete instances of abstractions that were perviously defined either by you or by somebody else. You are using the prepackaged behaviors provided by the abstraction (as designed by its creator). You think about, how can I use the available behaviors of this and other available abstractions to solve the problem at hand. You think about what does this abstraction represent and what can it do for me.
Object literals, class declarations, Object.define* methods, and construtor functions, and the proposed := operator are language constructs that are commonly used in the parts of a program that define new abstractions. They are used to build abstractions.
Property access, method invocations, and property assignment (=) are language constructs that are commonly used in the parts of programs that are interacting with instances of previously defined abstractions. They are used to interact with abstractions.
If you think about JS programming in this way the distinction between = and := seems quite clear.
For DOM elements and innerHTML it comes down to deciding which of the following applies:
- Do I want to update the innerHTML state of a DOM element instance? I need to use =
- Do I want to define a new kind of DOM element with unique innerHTL behavior? I need to use :=
There are irregularities around the edges such as when assignment auto defines a missing property but they are of minor importance if a programmer clearly understands where they are defining abstractions and where they are consuming abstractions.
I'd first like to state that I am extremely interested in having some construct for the described intention behind the := operator
Based on the discussion here what comes to mind would be having the following instead
Object.assign(obj, {...}); same as obj.{...};
Object.define(obj, {...}); same as obj:{...};
Specific method names for the Object.* could be decided upon. Would also be nice, following the above, to have the following
obj:prop = value;
On Aug 9, 2012, at 9:32 AM, Brendan Eich wrote:
David Herman wrote:
On Aug 8, 2012, at 5:01 PM, Brendan Eich wrote:
The problem is that neither = nor Object.defineProperty can be used succinctly and reliably to shadow or override.
OK, that's the real problem, thanks for making it clear -- sorry if I missed it before.
No problem. The "succinctly and reliably" conjunction is important, but it's not an argument for new syntax, by itself. I think we agree that an API such as Object.update would solve most of the problem, if not all ("succinctly"). Object.update could be polyfilled, to boot.
I think succinctness is a very important considerations in this case. One of the root problems is that it is just too darn easy to say = rather than Object.defineProperty or Object.update. Even those of us who know better take the shortcut and use = whenever we think we can get away with it.
I don't think any of the proposed procedural APIs are likely to change that habit. I think := stands a change. I'm pretty sure it would change my behavior.
So then the question becomes: how common should it be, then? Are the use cases it addresses common enough to warrant new syntax?
It's hard to say for sure, but I find the class "statics" use-case compelling, since maximin classes don't have any declarative support for class-side properties.
Especially syntax that looks like a new variation on an existing thing. ("Wait, which kind of assignment do I need to use here?")
That does look like trouble, now that you and Doug point it out. We have spiraled around .{ and := but not .= -- would .= be better?
Pushing devs to think "which kind of assignment do I need to use here?" and then acting appropriately is the whole point. The similarity between = and := encourages this thinking. Once a dev fully internalized the difference between property definitions and value assignment and which operator is associated with each concept they will seldom actually have to stop and think.
I don't think that .= is a better alternative than := as it less suggestive "define". I can imagine utility in having three = forms:
targetObj.prop = value //assign a value to a single property targetObj .= sourceObj //assign values from sourceObj properties to corresponding targetObj properties targetObj := sourceObj //define properties on targetObj that clone properties of sourceObj
If we only defined := now, we could still define .= in the future. If we define .= now to mean "define property" we don't have a good future alternative for "assign multiple property values"
On Aug 9, 2012, at 10:52 AM, Matthew Robb wrote:
I'd first like to state that I am extremely interested in having some construct for the described intention behind the := operator
Based on the discussion here what comes to mind would be having the following instead
Object.assign(obj, {...}); same as obj.{...};
Object.define(obj, {...}); same as obj:{...};
Unfortunately, this would be ambiguous with label: { /*a block */ };
Specific method names for the Object.* could be decided upon. Would also be nice, following the above, to have the following
obj:prop = value;
same here
On Thu, Aug 9, 2012 at 2:06 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Aug 9, 2012, at 10:52 AM, Matthew Robb wrote:
snip
obj:{...};
Unfortunately, this would be ambiguous with label: { /*a block */ };
Specific method names for the Object.* could be decided upon. Would also be nice, following the above, to have the following
obj:prop = value;
same here
But, also ambiguous with label
var a; label:a = true;
Is there no way to contextually tell that you are not within an object literal?
On Aug 8, 2012, at 6:14 PM, Brendan Eich wrote:
No problem. For anyone used to Pascal or Ada, or familiar with Go, := could indeed be a speedbump.
(or Smalltalk, ;-)
But not a big speed-bump. For a Pascal programmers moving directly to ES without any exposure to a C-syntax language, I'm pretty sure that =/:= confusion isn't going to be the biggest hurdle they will have to deal with.
Using similar syntax for similar semantics in different programming language makes it easer to initially pickup a new language. But to be competent in a new language you have to learn its syntactic subtleties . Even ES = and C = don't have identical semantics.
Maybe programmers are as practiced in learning new language as they were "back in the day" but I'd be willing to give them the benefit of the doubt in that regard.
Matthew Robb wrote:
Object.assign(obj, {...}); same as obj.{...};
Not the same as Allen's proposed mustache form, which defined not assigned. But mustache was taken back to the lab for more work, and we're on to := now.
Object.define(obj, {...}); same as obj:{...};
No can do, this is ambiguous with a labeled statement.
Specific method names for the Object.* could be decided upon. Would also be nice, following the above, to have the following
obj:prop = value;
Label again.
Matthew Robb wrote:
Is there no way to contextually tell that you are not within an object literal?
Label as in a labeled statement, not property name in object literal.
Allen Wirfs-Brock wrote:
On Aug 8, 2012, at 6:14 PM, Brendan Eich wrote:
No problem. For anyone used to Pascal or Ada, or familiar with Go, := could indeed be a speedbump.
(or Smalltalk, ;-)
But not a big speed-bump. For a Pascal programmers moving directly to ES without any exposure to a C-syntax language, I'm pretty sure that =/:= confusion isn't going to be the biggest hurdle they will have to deal with.
It's not just old programmers coming out of hypersleep (Ada is still used, but nm). The := spelling by itself is close to =, and Doug indeed thought it was a workalike. But it's really not.
To switch metaphors from speedbumps, there's smoke. I am not as confident as you that there is no fire.
I think it is pretty optimistic to suppose that some day down the road when := support is in all target browsers and runtimes, the convenience of := will mean developers choose it where they would otherwise choose = instead of using Object.defineProperty. They have to know they're defining not assigning. The convenience of = or inconvenience of Object.defineProperty doesn't enter into that "have to know" condition or whether it is satisfied.
People will always prefer = if there's confusion about assigning vs. defining, even with :=. Assignment is the "obvious thing".
On Aug 8, 2012, at 6:11 PM, David Herman wrote:
On Aug 8, 2012, at 5:01 PM, Brendan Eich wrote:
...
Allen should correct me, but I think he doesn't disagree, since he says:
And, := should not be thought of or become the common way to do assignment. That should remain the job of =
(That double-cited text is not mine, however -- it is from Allen, in reply to me to Doug -- but Allen is not on your To/cc list for some reason...)
Yes, I was purposefully quoting Allen (note my words, "Allen should correct me... he says:") from another message via copy/paste-quoted. I try to avoid needless proliferation of messages when I can condense into a single reply.
Yes, you correctly represented by point. Rick Waldron innerHTML example shows how misusing := for group assignment could go wrong. I'm not currently opposed to the possibility of a separate group assignment operator (eg, .=) put it wouldn't replace the need for :=
Those are the labels that can be used e.g. with break and continue. Not names of properties in an object literal.
[[[Sent from a mobile device. Please forgive brevity and typos.]]]
Dr. Axel Rauschmayer axel at rauschma.de Home: rauschma.de Blog: 2ality.com
Allen Wirfs-Brock wrote:
targetObj.prop = value //assign a value to a single property targetObj .= sourceObj //assign values from sourceObj properties to corresponding targetObj properties targetObj := sourceObj //define properties on targetObj that clone properties of sourceObj
If we only defined := now, we could still define .= in the future. If we define .= now to mean "define property" we don't have a good future alternative for "assign multiple property values"
But .= does not connote "assign" rather than "define". You make the case that := suggests "define" by reference to the : separating property name from value in object literals, and that's helpful. There's no dot analog for colon, however.
Indeed if sourceObj is an object literal, we'd have, e.g.,
targetObj .= {prop1: val1, prop2: val2}
and at least as many colons as dots.
The more I think about this, the more I doubt we can help people decide between assignment and definition just by giving both short syntax. The requirement to define rather than assign is independent of syntax at the redefinition point, inherent in the object model being used.
Further, assignment via = will remain the most common case, and the source of bugs to reduce by documentation and education, no matter the syntax of the fix. In teaching people how to make the fix or avoid the bug, you're right that short syntax can help, but it's not clear how much. As Dave points out, that depends on how often the need for define-not-assign arises.
I fear we're missing the opportunity to get into Object.update by getting stuck on syntax. The ES5 APIs are too verbose but that doesn't mean something like Object.extend should be left to libraries and not built in, as JSON and Function.prototype.bind were in ES5.
On Aug 9, 2012, at 18:48 , Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
There are irregularities around the edges such as when assignment auto defines a missing property but they are of minor importance if a programmer clearly understands where they are defining abstractions and where they are consuming abstractions.
Let’s, simplifyingly, call people who create abstractions “library authors” and people who use abstractions “normal developers”. It does make sense to give the latter group specialized tools that help them do their work. But I’d like to take a step back and ask the following question.
Where can the assignment operator (=) currently bite you? I’ve come up with the following list:
- Copying properties, where you always want to override and update [[Home]] (for super references).
- Patching non-writable configurable properties
- Adding a property to an object (without invoking the setter), e.g. to add a unique ID to an object you put into a collection
- Constructors should always define properties, not assign them. If you add instance methods, [[Home]] and [[MethodName]] must be set up properly.
Analysis:
(1) is only relevant for library authors. Normal developers can use Object.update() or similar (including an operator).
(2) is only done by library authors. They can use Object.define() et al.
(3) seems like an edge case, mostly relevant to library authors. With an object you know, assignment seems OK. With an object you haven’t created, overriding a setter with a data property seems like asking for trouble, anyway. In ES.next, private names should solve this problem.
(4) matters for normal developers, but only for instance methods. If I am not mistaken, instance methods are currently mostly used for Crockford-style privacy and will be much less common once we have private names. Furthermore, it is difficult if not impossible to add an instance method with a super-reference in ES.next. But that might change later.
Did I miss anything? I agree that we should protect normal developers from getting bitten by the assignment operator. But whatever the solution, it has to be dead-simple. Perhaps there are smaller measures that can be taken so that normal programmers don’t have to understand the difference between definition and assignment. Which is subtle, because by auto-creating missing properties, assignment has taken on some of definition’s responsibilities.
Axel Rauschmayer wrote:
On Aug 9, 2012, at 18:48 , Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>> wrote:
There are irregularities around the edges such as when assignment auto defines a missing property but they are of minor importance if a programmer clearly understands where they are defining abstractions and where they are consuming abstractions.
Let’s, simplifyingly, call people who create abstractions “library authors” and people who use abstractions “normal developers”. It does make sense to give the latter group specialized tools that help them do their work. But I’d like to take a step back and ask the following question.
It's a very big simplification. There is no "normal developer" in any at least middle-sized project, imnsho. We are all "library authors" most of the time - we create abstractions.
So I see the rest of the original post as not at all that strong as it may look.
On the contrary, we should definitely help "abstraction writers" to be able to do their work with ease and, which is connected, to help "consumer developers" become "abstraction writers" more easily.
I definitely agree with Allen's (especially the last sentence):
Let’s, simplifyingly, call people who create abstractions “library authors” and people who use abstractions “normal developers”. It does make sense to give the latter group specialized tools that help them do their work. But I’d like to take a step back and ask the following question.
It's a very big simplification. There is no "normal developer" in any at least middle-sized project, imnsho. We are all "library authors" most of the time - we create abstractions.
So I see the rest of the original post as not at all that strong as it may look.
Those names are not important, what matters is the list of use cases and their analysis. Do you only disagree with the names or also with the analysis?
There are some things that normal developers never do, for example: patching built-ins.
On Aug 9, 2012, at 2:30 PM, Herby Vojčík wrote:
Axel Rauschmayer wrote:
On Aug 9, 2012, at 18:48 , Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>> wrote:
There are irregularities around the edges such as when assignment auto defines a missing property but they are of minor importance if a programmer clearly understands where they are defining abstractions and where they are consuming abstractions.
Let’s, simplifyingly, call people who create abstractions “library authors” and people who use abstractions “normal developers”. It does make sense to give the latter group specialized tools that help them do their work. But I’d like to take a step back and ask the following question.
It's a very big simplification. There is no "normal developer" in any at least middle-sized project, imnsho. We are all "library authors" most of the time - we create abstractions.
So I see the rest of the original post as not at all that strong as it may look.
On the contrary, we should definitely help "abstraction writers" to be able to do their work with ease and, which is connected, to help "consumer developers" become "abstraction writers" more easily.
You beat me to it! Programs of even moderate size and complexity require abstraction. Arguably that's what real programmers do. If "library author" means "abstraction creators" then those are exactly the people we want to better support.
See the harmony goals harmony:harmony including:
Goals Be a better language for writing: complex applications; libraries (possibly including the DOM) shared by those applications; ...
Means ... Provide syntactic conveniences for: good abstraction patterns; high integrity patterns; ...
But you are criticizing the first paragraph of my email while ignoring the rest.
If we are to fix the problems caused by assignment, should we not first try to list those problems?
On Aug 9, 2012, at 1:49 PM, Brendan Eich wrote:
Allen Wirfs-Brock wrote:
targetObj.prop = value //assign a value to a single property targetObj .= sourceObj //assign values from sourceObj properties to corresponding targetObj properties targetObj := sourceObj //define properties on targetObj that clone properties of sourceObj
If we only defined := now, we could still define .= in the future. If we define .= now to mean "define property" we don't have a good future alternative for "assign multiple property values"
But .= does not connote "assign" rather than "define". You make the case that := suggests "define" by reference to the : separating property name from value in object literals, and that's helpful. There's no dot analog for colon, however.
Indeed if sourceObj is an object literal, we'd have, e.g.,
targetObj .= {prop1: val1, prop2: val2}
and at least as many colons as dots.
Well, strictly speaking the object literal that is the RHS of the above example is defining properties on a new object and those the properties of that object are then assigned to corresponding properties of the LHS object.
Regardless, the associations between the symbols and the semantics don't have to be perfect they just have to be close enough to have some mnemonic value.
The more I think about this, the more I doubt we can help people decide between assignment and definition just by giving both short syntax. The requirement to define rather than assign is independent of syntax at the redefinition point, inherent in the object model being used.
Further, assignment via = will remain the most common case, and the source of bugs to reduce by documentation and education, no matter the syntax of the fix. In teaching people how to make the fix or avoid the bug, you're right that short syntax can help, but it's not clear how much. As Dave points out, that depends on how often the need for define-not-assign arises.
I don't think the new syntax would directly help them understand their intention. That is something they need to understand, independent of syntax. But the existence of both = and := may encourage developing this level of understanding; simply by making the choice available.
The primarly purpose of new syntax shouldn't be pedagogical. Rather it should help and encourage good programmers to write good code. I want programmer who know they are defining a property to be able to easily express that intent rather than lazily using = even though they know better. I'm pretty sure I would always use :={...} over = in those situations. I not so sure that I would be disciplined enough to always use Object.update(...) rather than = in such situations.
I fear we're missing the opportunity to get into Object.update by getting stuck on syntax. The ES5 APIs are too verbose but that doesn't mean something like Object.extend should be left to libraries and not built in, as JSON and Function.prototype.bind were in ES5.
I certainly don't have any objection to have the procedural forms. Personally, I think Object.define and Object.assign would be a good names for the two concepts we have been talking about. Having such functions would actually make it easier to explain corresponding syntactic forms. Object.define(Point, { fromPolar(rho,theta) { return new this(rhoMath.cos(theta), rhoMath.sin(theta)) } }); is not as readable or writable as: Point := { fromPolar(rho,theta) { return new this(rhoMath.cos(theta), rhoMath.sin(theta)) } }; (note how "Point" gets visually lost in the procedural form. The eye focuses on "Object.define" rather than the more important "Point")
If := wasn't available I'd certainly want to have the procedural form available. All names are bikeshedable...
Allen Wirfs-Brock wrote:
Well, strictly speaking the object literal that is the RHS of the above example is /defining/ properties on a new object and those the properties of that object are then /assigned/ to corresponding properties of the LHS object.
Of course, but see below.
Regardless, the associations between the symbols and the semantics don't have to be perfect they just have to be close enough to have some mnemonic value.
Here I think you're going to run into trouble. The syntax has to be pretty sweet to meet general acclamation in the community and to achieve consensus in TC39.
Language design involves conservative reuse of older languages' design, some cross-cutting thinking that ideally makes maximum use of a few cleanly composing primitives, and no mercy for "close enough (but not quite right)". Symbology is not arbitrary, or no more arbitrary than history and tradition in the relevant CS subculture.
Yes, JS is in the C family so it has some unusual (to those not native-born to C or JS) parts. Idioms arise in all practical languages.
But .= is not the clear "assign not define" winner, and := is quite different from Algol et al. (Pascal, Ada), and Smalltalk (thanks for reminding me). If the goal is batch assignment, then writing the batch parts as property definitions in an object literal is mixing "define" with the outer, later "assign", and .= misplaces the dot. The batch assignment proposal goes the other way:
obj.{ prop1 = val1; prop2 = val2 ... }
with a more statement-like grammar within braces, yet without recreating 'with'.
I don't know that batch assignment will achieve consensus. The semantics with nested .{...} occurrences is more powerful than .=, which doesn't handle nesting (neither does := AFAICT). Whatever the semantics, getting syntax past the various taste-testers and grammar buffs is a tall order. I don't think "perfect" is possible but I do think "close enough to have some mnemonic value" is not sufficient.
If batch assignment including nesting is the winning semantics, the syntax probably follows and can't use .=. So reserving .= based on := (which is not yet in Harmony) seems premature. Sorry to push back, but after mustache I think we need to be pickier about new syntax.
And (to beat my favorite drum) we must not starve the polyfillable/no-new-syntax API alternatives (Object.assign, Object.define or Object.update) in the mean time, where new syntax is not required by new semantics.
Allen Wirfs-Brock wrote:
I certainly don't have any objection to have the procedural forms. Personally, I think Object.define and Object.assign would be a good names for the two concepts we have been talking about. Having such functions would actually make it easier to explain corresponding syntactic forms. Object.define(Point,{ fromPolar(rho,theta) { return new this(rhoMath.cos(theta), rhoMath.sin(theta)) } }); is not as readable or writable as: Point :={ fromPolar(rho,theta) { return new this(rhoMath.cos(theta), rhoMath.sin(theta)) } }; (note how "Point" gets visually lost in the procedural form. The eye focuses on "Object.define" rather than the more important "Point")
Agreed, as noted I think the class statics use-case is strong (but if we added class static declarative syntax, then maybe there's no strong use-case for := left).
If := wasn't available I'd certainly want to have the procedural form available.
Here I think we still disagree. := is not enough, it requires a compiler. We want the API and it will see use including polyfills. Given that projection, I'm not sure how much := use we'll see in the short run, but I contend that := does not relieve us from having the API form.
All names are bikeshedable...
We were making progress, don't back off. Object.assign and Object.define.
Based upon discussions last week in the "July 25, 2012 - TC39 Meeting Notes" thread, I've created a new strawman proposal for a := operator. See strawman:define_properties_operator
:= is a convient way to copy properties from one object to another or to extend an object with new properties. It combines supports for many of the same use cases as the previously proposed "object extension literals" and the JSFixed Object.extend proposal.
The most important characteristic of := is that it uses [[DefineOwnProperty]] semantics rather than [[Put]] semantics to define properties on its target object so it doesn't run into issues with assignment to accessor properties or over-riding inherited readonly properties. It is also smart about dealing with methods that reference super.
Some basic examples:
target := src; //define all own properties of src onto target
//add a method and an accessor to an existing prototype Point.prototype := { plus(aPoint) {return new this.comstructor(this.x+aPoint.x,this.y+aPoint.y}, get rho() {return Math.sqrt(this.xthis.x+this.ythis.y} };
Have at it,