Proposal: Object.defineProperty shorthand

# Sean Eagan (14 years ago)

Note: This proposal stems from the discussion in [3]. The intention is to update [1] and [2] and either include the new content within [1] or a new strawman linked to from [2].

Object.defineProperty shorthand

Object.defineProperty calls are verbose, and also require property descriptor object allocation and validation. Both of these issues could be solved by providing concise and optimizable syntax for this functionality. Allen Wirf-Brock's "Concise Object Literal Extensions" strawman [1] already solves this within the context of object literals, however, its infix non-writable mark makes it non-applicable to other contexts. This can be fixed by making it a prefix, and adjusting the prefix-character to property-descriptor-attribute mapping.

Semantics:

// # implies non-configurable

a.b = c

// <=>

Object.defineProperty(a, "b", {configurable: false, writable: true, enumerable: true, value: c})

// ! implies non-writable, ~ implies non-enumerable // all assignment operators can be used ! a.b += c // <=>

Object.defineProperty(a, "b", {configurable: true, writable: false, enumerable: true, value: a.b + c})

// postfix expressions only work when # (non-configurable) is used !~a.b++ // <=> !(~(a.b++)) #~a.b++ // evaluates to initial value of a.b just like a.b++ does // <=>

Object.defineProperty(a, "b", {configurable: false, writable: false, enumerable: true, value: a.b++})

// can use both dot and bracket syntax #!~ a[b] = c // <=>

Object.defineProperty(a, b, {configurable: false, writable: false, enumerable: false, value: c})

// destructuring works as well [#a.b, !this[c], ...~d.e] = [1, 2, 3, 4]; // <=>

Object.defineProperty(a, "b", {configurable: false, writable: true, enumerable: true, value: 1}); Object.defineProperty(this, c, {configurable: true, writable: false, enumerable: true, value: 2}); Object.defineProperty(d, "e", {configurable: true, writable: true, enumerable: false, value: [3, 4]});

// changes to object literals var a = { #!~b: "b" // infix = changed to prefix !, previous ! changed to # !c // non-writable implicit initialization parameters now work }

Grammar changes to [2] :

LeftHandSideExpression : DataPropertyPrefixopt BasicLeftHandSideExpression

BasicLeftHandSideExpression : NewExpression CallExpression

PrefixedPropertyAssignment: DataPropertyPrefixopt DataPropertyAssignment AccessorPropertyPrefixopt AccessorPropertyAssignment

Note: the following two replace PropertyPrefix

DataPropertyPrefix : ! AccessorPropertyPrefix AccessorPropertyPrefix ! ! AccessorPropertyPrefix

AccessorPropertyPrefix :

~ ~# #~

Note: the following two replace PropertyAssignment

DataPropertyAssignment : Identifier PropertyName : AssignmentStatement PropertyName ( FormalParameterListopt ) { FunctionBody }

AccessorPropertyAssignment : get PropertyName ( ) { FunctionBody } set PropertyName ( PropertySetParameterList ) { FunctionBody } set super get PropertyName ( ) { FunctionBody } get super set PropertyName ( PropertySetParameterList ) { FunctionBody }

[1] strawman:concise_object_literal_extensions [2] strawman:basic_object_literal_extensions [3] esdiscuss/2011-May/014566

Thanks, Sean Eagan

# Mike Shaver (14 years ago)

On Thu, May 26, 2011 at 11:37 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

// ! implies non-writable, ~ implies non-enumerable // all assignment operators can be used ! a.b += c

Legal parse today, though I'm not sure you can get runtime semantics that aren't an error.

!~a.b++ !(~(a.b++))

Legal expressions today with valid meanings.

Mike

# Sean Eagan (14 years ago)

On Thu, May 26, 2011 at 1:43 PM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Thu, May 26, 2011 at 11:37 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

// ! implies non-writable, ~ implies non-enumerable // all assignment operators can be used ! a.b += c

Legal parse today, though I'm not sure you can get runtime semantics that aren't an error.

Correct, I was intending for it to no longer be an error.

!~a.b++ !(~(a.b++))

Legal expressions today with valid meanings.

Yes, I was trying to show that this shorthand would not be applicable to postfix expressions unless you prefix with #. Since that could be confusing, we could just make it never work with postfix expressions via:

PostfixExpression: BasicLeftHandSideExpression

Thanks, Sean Eagan

# Sean Eagan (14 years ago)

On Thu, May 26, 2011 at 1:54 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

PostfixExpression:  BasicLeftHandSideExpression

Sorry, I meant:

PostfixExpression : BasicLeftHandSideExpression BasicLeftHandSideExpression [no LineTerminator here] ++ BasicLeftHandSideExpression [no LineTerminator here] --

Thanks, Sean Eagan

# David Flanagan (14 years ago)

Is a not-quite-so-cryptic shorthand possible? Could you use #readonly, #hidden, and #fixed instead of !, ~ and #, for example? This would be more extensible. Maybe #sealed and #frozen for object, literals?

# Mike Shaver (14 years ago)

On Thu, May 26, 2011 at 11:54 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

On Thu, May 26, 2011 at 1:43 PM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Thu, May 26, 2011 at 11:37 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

// ! implies non-writable, ~ implies non-enumerable // all assignment operators can be used ! a.b += c

Legal parse today, though I'm not sure you can get runtime semantics that aren't an error.

Correct, I was intending for it to no longer be an error.

I don't understand how that could work. By the time you get the runtime error (trying to increment a boolean value), we have already performed a property access, which can have side-effects. More practically, an engine would have to reconstruct the original syntactic form from the runtime result.

addto(c, not(get(a, "b")))

Mike

# Sean Eagan (14 years ago)

On Thu, May 26, 2011 at 2:57 PM, David Flanagan <dflanagan at mozilla.com> wrote:

Is a not-quite-so-cryptic shorthand possible?

I tried to choose the characters to be as non-cryptic as possible using the following mnemonic devices:

- non-configurable:

corresponds to frozen which # represents for records, tuples etc.

! - non-writable: It's the "NOT" operator. The lighter weight of ! vs # matches the lighter restriction of non-writable vs non-configurable.

~ - non-enumerable (matches Allen's original suggestion): It's the "BITWISE NOT" operator. One could imagine objects internally storing a list of bits E, each of which is set if their corresponding object property specified this mark when being created. Then ~E represents the list of bits corresponding to the object's enumerable properties.

Could you use #readonly, #hidden, and #fixed instead of !, ~ and #, for example?

I think it may be confusing to introduce different names for the "non-" version of properties, thus, it would probably be better to just use the full attribute names, however, then you would lose much of the conciseness gained.

This would be more extensible.

That is true, although I'm not sure what the likelihood is of any new property descriptor attributes being added in the future? Does anyone have any thoughts on that?

Maybe #sealed and #frozen for object, literals?

I've been working on a concept called "object descriptors" as the object level equivalent of property descriptors, still working on it, will send something out when complete. But, for the declarative syntax for it, I am thinking along the lines of an "annotates" operator signified by @ :

// create a proxy var proxy = {prototype: P, handler: H} @ { x: "x" }

// reusable descriptor var nonExtensibleP = {prototype: P, extensible: false};

var x = nonExtensibleP @ { x: "x" }

Something similar could work for properties, need to think about it.

Thanks, Sean Eagan

# Sean Eagan (14 years ago)

On Thu, May 26, 2011 at 4:27 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

Something similar could work for properties, need to think about it.

Here's a start:

// reusable descriptor var allFalse = {configurable: false, writable: false, enumerable: false};

var foo = { {configurable: false} @ a: 1, // writable, enumerable default to true allFalse @ b: 2, // dynamic #!~ @ c: 3 // shorthand still works,

@ get x() {return "x"}

set x() {return "x"} // only specify for get xor set, not both }

// property assignment versions {configurable: false} @ foo.d = 4 allFalse @ foo["e"] = 5; #!~ @ foo.f = 6;

Thanks, Sean Eagan

# Sean Eagan (14 years ago)

Sorry Mike, just saw your response. I think it would use the same parsing technique as destructuring, the left hand side would need to be post-processed. My grammar might need to be updated to make that work though.

The pattern matching strawman [1] has a note about this:

Destructuring assignment is parsed as an LHSExpression but then post-processed as a Pattern(false). Note that this non-terminal represents a subset of LHSExpression.

[1] strawman:pattern_matching

On Thu, May 26, 2011 at 4:06 PM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Thu, May 26, 2011 at 11:54 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

On Thu, May 26, 2011 at 1:43 PM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Thu, May 26, 2011 at 11:37 AM, Sean Eagan <seaneagan1 at gmail.com> wrote:

// ! implies non-writable, ~ implies non-enumerable // all assignment operators can be used ! a.b += c

Legal parse today, though I'm not sure you can get runtime semantics that aren't an error.

Correct, I was intending for it to no longer be an error.

I don't understand how that could work.  By the time you get the runtime error (trying to increment a boolean value), we have already performed a property access, which can have side-effects.  More practically, an engine would have to reconstruct the original syntactic form from the runtime result.

addto(c, not(get(a, "b")))

Mike

Thanks, Sean Eagan

# Sean Eagan (14 years ago)

I still like the syntax proposed at the beginning of the thread the best.

I know the concern from TC39 at the May meeting with object literal extensions [1] was that the syntax was "punctuation soup". Does co-locating all 3 characters as a single prefix help with this? I think it would have to be single characters for each attribute, if you use the full names you are hardly gaining enough conciseness to make it worthwhile.

On Thu, May 26, 2011 at 6:24 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

On Thu, May 26, 2011 at 4:27 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

Something similar could work for properties, need to think about it.

Here's a start:

// reusable descriptor var allFalse = {configurable: false, writable: false, enumerable: false};

var foo = {  {configurable: false} @ a: 1, // writable, enumerable default to true  allFalse @ b: 2, // dynamic  #!~ @ c: 3 // shorthand still works,  # @ get x() {return "x"}  set x() {return "x"} // only specify for get xor set, not both }

// property assignment versions {configurable: false} @ foo.d = 4 allFalse @ foo["e"] = 5; #!~ @ foo.f = 6;

Thanks, Sean Eagan

[1] strawman:concise_object_literal_extensions

Thanks, Sean Eagan

# Brendan Eich (14 years ago)

On May 31, 2011, at 11:52 AM, Sean Eagan wrote:

I still like the syntax proposed at the beginning of the thread the best.

I like # for non-configurable, ! for non-writable, and ~ for non-enumerable too. All prefixes. But not for assignment expressions as I think you showed -- only in object initialisers and classes.

I know the concern from TC39 at the May meeting with object literal extensions [1] was that the syntax was "punctuation soup". Does co-locating all 3 characters as a single prefix help with this? I think it would have to be single characters for each attribute, if you use the full names you are hardly gaining enough conciseness to make it worthwhile.

Agreed. We've swerved from a verbose proposal at the March meeting, where Allen gamely tried to reuse existing keywords, then generalized and regularized to spell everything out; back to the terse punctuator approach we saw at May, but with := after the property name for read-only.

Using prefixes only seems strictly better than !~ before and := after. Also, using # for non-configurability lines up with hash-arrows or hash-functions as frozen/joined, records, and tuples. Although in your proposal # means only non-configurable, not also non-writable.

A Unix-y (ls(1) inspired) mid-point might be to use strings:

var foo = { {"!c!e!w"} high_integrity: 42, ... };

or even

var foo = { {"!(cew)"} high_integrity: 42, ... };

but now there's a little language encoded in the string. We would need some rules to future-proof the design. At least this would be more concise than the most-verbose proposal.

My view is that there's no middle way. We should take the punctuation soup hit, using the right punctuators, to make up for the verbosity tax of ES5's property descriptors. # for !c, ~ for !e, and ! for !w seem best at the moment.

This is surely a bikeshedding opportunity but it's also all about user interface. Warm up your paint-sprayers!

/be

On Thu, May 26, 2011 at 6:24 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

On Thu, May 26, 2011 at 4:27 PM, Sean Eagan <seaneagan1 at gmail.com> wrote:

Something similar could work for properties, need to think about it.

Here's a start:

// reusable descriptor var allFalse = {configurable: false, writable: false, enumerable: false};

var foo = { {configurable: false} @ a: 1, // writable, enumerable default to true allFalse @ b: 2, // dynamic #!~ @ c: 3 // shorthand still works,

@ get x() {return "x"}

set x() {return "x"} // only specify for get xor set, not both }

// property assignment versions {configurable: false} @ foo.d = 4 allFalse @ foo["e"] = 5; #!~ @ foo.f = 6;

P.S. these are not good ideas. The first two would entail restricted productions that are too close to erroneous input today, where adding a newline triggers ASI and removes the error.

# Allen Wirfs-Brock (14 years ago)

I really don't see great value in trying to use this technique to set attributes for properties created via direct assignment. The syntax issues are large and assuming that we are able to provide attribute flags in both object literals and class declaration I just don't see that many cases where creation by assignment is will be needed.

On May 31, 2011, at 12:18 PM, Brendan Eich wrote:

On May 31, 2011, at 11:52 AM, Sean Eagan wrote:

I still like the syntax proposed at the beginning of the thread the best.

I like # for non-configurable, ! for non-writable, and ~ for non-enumerable too. All prefixes. But not for assignment expressions as I think you showed -- only in object initialisers and classes.

This is as plausible as using ! for non-configurable as I had proposed. It really comes down to alignment with other uses of #. It is pretty easy to make a logical association between this mapping of symbols to meanings. However, it would be pretty cryptic if we every needed to add a fourth or fifth attribute systems.

I know the concern from TC39 at the May meeting with object literal extensions [1] was that the syntax was "punctuation soup". Does co-locating all 3 characters as a single prefix help with this? I think it would have to be single characters for each attribute, if you use the full names you are hardly gaining enough conciseness to make it worthwhile.

Agreed. We've swerved from a verbose proposal at the March meeting, where Allen gamely tried to reuse existing keywords, then generalized and regularized to spell everything out; back to the terse punctuator approach we saw at May, but with := after the property name for read-only.

Using prefixes only seems strictly better than !~ before and := after. Also, using # for non-configurability lines up with hash-arrows or hash-functions as frozen/joined, records, and tuples. Although in your proposal # means only non-configurable, not also non-writable.

concise method properties may create a problem for # meaning non-configurable. the object literal proposal currently allows:

var foo ={ bar() { } };

as short hand for: var foo = { ~bar: function bar() {} }

but if #bar() {} or #baz()->{}

is valid in general expressions, then what is the meaning of: var foo = { #bar() {}, #baz()->{} }

is it:

var foo = { #bar: #bar() {}, #baz: #baz()->{} } or var foo = { bar: #bar() {}, baz: #baz()->{} }

A Unix-y (ls(1) inspired) mid-point might be to use strings:

var foo = { {"!c!e!w"} high_integrity: 42, ... };

or even

var foo = { {"!(cew)"} high_integrity: 42, ... };

but now there's a little language encoded in the string. We would need some rules to future-proof the design. At least this would be more concise than the most-verbose proposal.

but I don't think it would make those who object to "punctuation soup" any happier

My view is that there's no middle way. We should take the punctuation soup hit, using the right punctuators, to make up for the verbosity tax of ES5's property descriptors. # for !c, ~ for !e, and ! for !w seem best at the moment.

I agree

# Axel Rauschmayer (14 years ago)

From: Brendan Eich <brendan at mozilla.com>

A Unix-y (ls(1) inspired) mid-point might be to use strings:

var foo = { {"!c!e!w"} high_integrity: 42, ... };

Would it make sense to turn this into a generic system for adding meta-data to properties? Python’s decorators and Java’s annotations come to mind which are used for various great purposes in libraries. Some of Java’s annotations such @Override or @Nullable also cross the border into language territory.

Won’t enumerable go away (as in “used less”) in the long run? Thus there is less of a reason to provide a concise syntax for specifying enumerability.

I would prefer something like

var foo = { { !conf !enum !write } high_integrity: 42, ... };

Yes, it is more verbose, but also more descriptive.

# Brendan Eich (14 years ago)

On May 31, 2011, at 2:25 PM, Allen Wirfs-Brock wrote:

This is as plausible as using ! for non-configurable as I had proposed. It really comes down to alignment with other uses of #. It is pretty easy to make a logical association between this mapping of symbols to meanings. However, it would be pretty cryptic if we every needed to add a fourth or fifth attribute systems.

I hope we don't add attributes without strong reason. Any more in sight?

but if #bar() {} or #baz()->{} is valid in general expressions, then what is the meaning of: var foo = { #bar() {}, #baz()->{} }

is it:

var foo = { #bar: #bar() {}, #baz: #baz()->{} } or var foo = { bar: #bar() {}, baz: #baz()->{} }

Per Mark's const functions and the const class adjective, I'd see and raise the former:

var foo = { #!~bar: #bar() {}, #!~baz: #baz()->{} }

The idea is to provide maximum integrity. Anything else, you'll have to spell out with punctuation, property name, colon, and the value.

IIRC, methods already imply ~ in your proposal, so that's expanded above in my bid ;-).