Extended dot notation (pick notation) proposal
- Take a look at esdiscuss.org/topic/pick-operator.
- Do keep in mind syntax additions tend to have a steep usefulness requirement in practice (e.g. Async functions) to make it into the language. And most definitely check out esdiscuss.org/topic/the-tragedy-of-the-common-lisp-or-why-large-languages-explode-was-revive-let-blocks.
Not saying this is a bad thing to add, but I'm skeptical on how likely it is to get accepted. Isiah Meadows me at isiahmeadows.com
Thanks. Yes, I should have mentioned the earlier proposal, which I've now marked as being superceded.
I'm aware the bar is high for syntax additions, as it should be. Some possible justifications include:
-
The need for the picking feature is demonstrated by the
_.pick
utility provided by toolbelts such as Underscore. It is also the topic of frequent questions on Q&A sites like SO. -
The proposed syntax changes are non-intrusive and would have little to no impact on the rest of the language AFAICT.
-
The proposal subsumes other proposals for
Array#last
etc. with the syntaxa.(-1)
, and proposals for "null propagation" with the syntaxa.b!
etc. Two birds with one stone.
Bob Myers
Here's another StackOverflow question stackoverflow.com/questions/39299046/destructuring-a-subset-of-an-object-directly-into-a-new-object
essentially asking for pick notation. This same question pops up regularly. It's the real world talking to us telling us what they need. In this particular case, the solution with extended dot notation would just be
var goodKeys = this.state.{toDate, fromDate, location, flavor};
Perhaps I have not posted the minimal version of my proposal for this pick notation rtm/js-pick-notation/blob/master/minimal/spec.md, also
known as extended dot notation. This version sacrifices some features in favor of complete compatibility with existing destructuring syntax following the dot.
There is another good reason these days for thinking about something about this: the advent of typed dialects of JS. We already know that a simple lodash-type version of pick, such as
function pick(o, keys) {
return Object.assign({}, ...keys.map(key => ({[key]: o[key]})));
}
const o = {a:1, b: 2};
const o2 = pick(o, ['a']);
cannot handle renaming or defaults or deep picking, but in addition it
cannot also not be typed properly.
For instance, lodash.d.ts
defines pick
as:
pick<TResult extends {}, T extends {}>(
object: T,
...predicate: (StringRepresentable|StringRepresentable[])[]
): TResult;
with no type safety whatsoever.
In contrast, with pick notation
const o2 = o.{a};
o2
can be precisely typed by the compiler as an object containing the
property a
, or a type compatible or assignable to that type.
-- Bob
TypeScript has a fair number of proposals aiming to address this (things like difference types, partial types, etc.), but in general, I find it just as easy to do it this way (which is easily checked):
const {toDate, fromDate, location, flavor} = this.state;
const goodKeys = {toDate, fromDate, location, flavor};
Hmm I gotta say I must have re-read that minimally extended dot notation proposal a few times and I just find the syntax confusing. I do like the idea of building a way of taking a portion of an object out of one object and into another but I don't think we need to provide additional syntax rules to handle this. Why couldn't this be an Object.pick() addition where it works similar to the underscore implementation? It could even be expanded to handle deep nesting (I'm actually adding this to my msngr.js library in the next release as grabbing a subset of an object is crazy useful and I need to do it far too frequently).
Isiah's workaround works but has the unfortunate side affect of copying values / increasing reference counts to objects. I'd love to see a built in solution.
Isiah's workaround works but has the unfortunate side affect of copying values / increasing reference counts to objects. I'd love to see a built in solution.
You'd have to do that anyways, even if it's entirely internal. How do you think you would do it outside of copying the values?
Here is a little decision tree of the issues.
[image: Inline image 1]
I'm just saying those references still exist, and ignoring them will lead you into more problems than this would solve. First, my above workaround would likely be the desugaring in V8. You could optimize it by creating the actual object and assigning the properties as they come from the object, but that's also a valid optimization for engines to make today with my workaround. Also, most other optimizations are equally permissible with both syntax and my equivalent workaround.
And this isn't C: unless you're using those references, JS engines will clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
As you can see, I'm generally against this.
I'll note that the type checking part will eventually get fixed in TypeScript, at least.
Oh, and I've already suggested another, more specific, pick
syntax (that
operation is only a shorthand for x => x.property
) ,but it ultimately got
rejected since no one could really agree to anything different other than the current state was workable, but not optimal. You may appreciate reading the entire conversation, as it's pretty informative and is related to this.
Here's the specific comment proposing that idea: tc39/proposal-bind-operator#24
Here's the issue where most the related discussion took place: tc39/proposal-bind-operator#26
I'm just saying those references still exist, and ignoring them will lead
you into more problems than this would solve.
Not suggesting anything of the sort only that your workaround adds additional variables with possible object references and a more native implementation could work around that. Nothing more.
And this isn't C: unless you're using those references, JS engines will
clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
Hmm this is interesting. It's been a while since I last memory profiled a web application but the last time if I kept any references to, say, DOM elements in any variables even if I was no longer using them they would still be eating up the memory causing memory leaks. Are you saying this is no longer the case? You don't need to necessarily get rid of all references to, say, a DOM element before the garbage collector will sweep it up?
I'm more curious than anything else; it appears my understanding here might be wrong so I want to dig into it more.
Oh, and I've already suggested another, more specific,
pick
syntax
Good read. Interesting. Honestly I'm more of a fan of introducing functions if possible to handle much of this instead of new syntax though I don't know if that is necessarily supported by others and that proposal goes a bit before what I was originally thinking.
On Wed, Sep 7, 2016, 04:10 Kris Siegel <krissiegel at gmail.com> wrote:
I'm just saying those references still exist, and ignoring them will lead you into more problems than this would solve.
Not suggesting anything of the sort only that your workaround adds additional variables with possible object references and a more native implementation could work around that. Nothing more.
And this isn't C: unless you're using those references, JS engines will clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
Hmm this is interesting. It's been a while since I last memory profiled a web application but the last time if I kept any references to, say, DOM elements in any variables even if I was no longer using them they would still be eating up the memory causing memory leaks. Are you saying this is no longer the case? You don't need to necessarily get rid of all references to, say, a DOM element before the garbage collector will sweep it up?
If the DOM element is not live and either is not referenced, or referenced only by things that are marked for collection, the GC will mark it (assuming they are only referenced by it). Then after a while, it's swept away with the rest of the garbage.
(Technically, this is an oversimplification, but it's a complex topic. Also, this doesn't apply to really old IE, which uses reference counting instead.)
I'm more curious than anything else; it appears my understanding here might be wrong so I want to dig into it more.
You do, but extra syntax for pick
makes 0 difference in this area. My
point is that the new references in between are created and retained
regardless of how you create them.
Oh, and I've already suggested another, more specific,
pick
syntaxGood read. Interesting. Honestly I'm more of a fan of introducing functions if possible to handle much of this instead of new syntax though I don't know if that is necessarily supported by others and that proposal goes a bit before what I was originally thinking.
I agree, too. And for individual properties, it is helpful, but this
language isn't quite as functional as people want to think. Also, it's not
like the alternatives are much larger: x => x.property
(and for the
original proposal's shorthand: (...xs) => console.log(...xs)
).
I just find the syntax confusing
It combines two syntaxes we know and love: (1) dot notation o.b
and (2)
deconstructing notation {a} = o
, into o.{a}
.
Maybe some have problems with the dot in a.{o}
being too inconspicuous.
At the cost of using up a precious special character, we could go with o # {a}
instead. Actually I think o pick {a}
is syntactically unambiguous.
I do like the idea of building a way of taking a portion of an object out of one object and into another
Good, many people seem to agree.
Why couldn't this be an Object.pick()
Personally, passing around properties as strings grates on me. Then there's
the issue of typability, although Isiah has pointed out a useful TypeScript
proposal that does deal with that. Within the _.pick
paradigm, it's hard
to envision a concise syntax for defaults and renaming, which the
deconstructing-like syntax gives us for free.
It could even be expanded to handle deep nesting
Just curious, what sort of interface to your _.pick
-like thing do you
have in mind? Will it support defaults and renaming?
By the way, I was looking at Elm and found it supports one advanced feature
of extended dot notation, which is the unary dot as function, as in
arr.map(.p)
.
Bob
Since some people seem to find the proposed pick notation o.{p1, p2}
, (to
create an object composed of the p1
and p2
properties from o
)
confusing, I am making an alternative proposal which hews more closely to
existing deconstructing assignment.
The definition of the new syntax is:
If a deconstructing assignment is wrapped in curly brackets
{}
, the result is a new object where the deconstructed names and values become property names and values.
The above case would be written as:
{ {p1, p2} = o }
and would result in
{ p1: o.p1, p2: o.p2 }
AFAICT the above syntax should be uniquely parseable. The advantage of this syntax is the minimal cognitive footprint: if you know how to deconstruct an object into variables using deconstructing assignment, then you can deconstruct an object into properties on a new object with nothing more than an extra set of surrounding curlies.
I will be adding this alternative syntax to the repo here rtm/js-pick-notation and here rtm/js-pick-notation/blob/master/minimal/spec.md in
the near future.
Bob
People in the real world continue to wonder why they can't pick/destructure from objects into objects, instead of just variables.
stackoverflow.com/questions/39602360/es6-destructuring-reassignment-of-object?noredirect=1#39602360
This is actually the second such question on SO in the last week.
Bob
I think that for now, personally, this should be in userland. Currently, making your own functions provides much more flexibility than the need of the operator in general. What I mean by that, is that in traditional ways, you'd have the need to pick your values of either an array or object by yourself.
In the pragmatic approach, you'd need to write an utility anyway, as it is not common to fetch only one value from the destruct. You'd pick what you need. Similarly, that's how generators and functions are about too.
As for the proposal, it might seem like a good thing to have this feature, but eventually, in big scale and even lesser projects, you'd have a tendency and indeed the need to, as well, to do this yourself so you'd pick out x, if y is present.
I'm into this, but it seems like the usage of the feature itself, even though how good it is, is undermined when it comes to the actual use case.
From userland, its features should be good for all scales, but this
seems like an single issue and is more about how to retrieve data in a special use case rather than at regular basis.
From SO, there's indeed various of issues in different use cases, but they
seem a lot like architectural issues in react rather than the purest form of ES6. There's different issues, but the same answer.
Taking a stand from the negativity ( finally :) ), it's a good feature, but it seems like we can do this on the plain level without involving new operators.
It's a good feature request in its nature, but the shape of it seems a off, when there's other ways.
Contact me on twitter or mail if you want more input, @ev1stensberg. Again, this is my perspective, if someone have arguments that enforces or makes mine less relevant, please do elaborate!
ES
On Tue, Sep 20, 2016 at 2:38 PM, Bob Myers <rtm at gol.com> wrote:
People in the real world continue to wonder why they can't pick/destructure from objects into objects, instead of just variables.
stackoverflow.com/questions/39602360/es6- destructuring-reassignment-of-object?noredirect=1#39602360
This is actually the second such question on SO in the last week.
Well, this not as weighty a consideration as you might think.
Some people on a web site are curious to know if they can reduce 2 lines of JS code to 1 line. Their shared curiosity may be a coincidence; or it may be the language's fault. Presume the latter. It doesn't follow that the answer should be yes. Since all new syntax imposes some mental load on all language users, the answer should be no unless the benefit is really dramatic, which I don't think it is here.
On Wed, Sep 21, 2016 at 10:40 AM, Jason Orendorff <jason.orendorff at gmail.com
wrote:
Since all new syntax imposes some mental load on all language users, the answer should be no unless the benefit is really dramatic, which I don't think it is here.
For the most part I can completely agree with the sentiment that all new syntax imposes some mental load on all language users but my question is how this applies when syntax RESTRICTIONS that seem counter-intuitive impose their OWN mental load.
I think the inspiration of this thread and the few before it comes from the fact that destructuring syntax and object literal shorthand syntax seem to share in a root syntax yet the two features are incompatible which makes the mental load of the restrictions more obvious.
- Matthew Robb
Some people on a web site are curious to know if they can reduce 2 lines of JS code to 1 line
Right, some people wanted to reduce 2 lines of JS code
const a = o.a;
const b = o.b;
to 1 line
const {a, b} = o;
and that's how we ended up with destructuring assignment, which managed to make its way into the spec. Actually it's not just one less line; it's more semantic and readable and less bug-prone.
I'm not giving excessive weight to the questions of a few folks on StackOverflow, but it is curious how frequent and obvious the questions of these real people writing real code are: "If I can deconstruct into variables, why can't I deconstruct into objects?"
It's worth pointing out that on a scale of 0 to 100 of syntactical "newness", this proposal ranks quite low. We're not introducing new operators, for example. We're adding the option to specify existing deconstructing syntax after the existing dot syntax to obtain an object with those deconstructed properties.
Another answer to this thread suggested that picking properties into a new
object could be handled by userland code. Sure it could, and already is, in
the form of Underscore's _.pick
etc. So yes, this is syntactic sugar. But so is
deconstructing assignment.
But it's a tiny bit more than mere syntactic sugar. Writing obj.{p1, p2}
is just sugar for writing {p1: obj.p1, p2: obj.p2}
. But I can also write
obj.{p1: q1, p2 = 100}
to take advantage of the familiar ability of
deconstructing syntax to rename and specify defaults, not to mention
picking deeply, or using computed property names. That is something that
would be very hard to implement in a userland function--ok, maybe not
difficult, but cumbersome--do you really want to specify deep picks as
"a.b.c"
?. And it would be a real challenge to write a TypeScript
signature for any version of _.pick
, much less one that handled renaming,
defaults, and deep picking.
I totally agree with Matthew Robb's comment in a separate response that this proposal is not imposing a new cognitive burden; it's REMOVING the cognitive burden of wondering why our handy deconstructing syntax cannot be used to pick properties into objects.
-- Bob
How about obj{p1, p2}
(without the dot)? obj{p1}.p1 === obj.p1
for
example seems more elegant to me than obj.{p1}.p1 === obj.p1
. I'm not
sure if that syntax would be ambiguous though.
Interesting idea, but I'm not too thrilled at the idea of imputing semantics of any kind to the mere juxtaposition of two constructs. And the dot already has the clear meaning of "taking" something (currently, a single property as a scalar) from an object, so it would seem to make sense to extend that existing meaning.
On Wed, Sep 21, 2016 at 12:33 PM, Matthew Robb <matthewwrobb at gmail.com>
wrote:
On Wed, Sep 21, 2016 at 10:40 AM, Jason Orendorff < jason.orendorff at gmail.com> wrote:
Since all new syntax imposes some mental load on all language users, the answer should be no unless the benefit is really dramatic, which I don't think it is here.
For the most part I can completely agree with the sentiment that all new syntax imposes some mental load on all language users but my question is how this applies when syntax RESTRICTIONS that seem counter-intuitive impose their OWN mental load.
Neither proposal can reasonably be seen as merely lifting unnecessary restrictions. Emphasizing some words doesn't make this seem like less of a stretch, to me.
I think the inspiration of this thread and the few before it comes from the
fact that destructuring syntax and object literal shorthand syntax seem to share in a root syntax yet the two features are incompatible which makes the mental load of the restrictions more obvious.
If it were a matter of lifting restrictions, this would be a very different
conversation. I get that to you, that's all it is. But I have trouble
seeing how that is true in any objective sense. In the spec, this will look
like a bunch of new text and syntactic productions. And nobody is going to
look at {{x, y} = obj}
and recover the meaning from previously learned
principles of the language. It's an extra thing to learn.
And of course it comes with its own "restrictions", as you put it; i.e. it cannot do everything either. Now people will wonder if they can put some of an object's properties in one new object and dump all the rest in another. Or if there's a shorthand for
let obj = complicated_expression();
let result = {start: {{x, y}=obj}, size: {{w, h}=obj}};
that eliminates the temporary obj
. And on and on forever.
On Tue Sep 20 03:38 PM, Bob Myers wrote:
People in the real world continue to wonder why they can't pick/destructure from objects into objects, instead of just variables.
stackoverflow.com/questions/39602360/es6-destructuring-reassign ment-of-object?noredirect=1#39602360
Seems like allowing to "dot" into another identifier could work: tc39.github.io/ecma262/#prod-CoverInitializedName
CoverInitializedName[Yield]: IdentifierReference[?Yield] Initializer[+In, ?Yield] IdentifierReference[?Yield] . IdentifierName
const IDENTIFIER = 1; const sandwichesIWantToEat = { SANDWICHES.CHEESE_STEAK, SANDWICHES.SLOPPY_JOE, IDENTIFIER };
Use the RHS identifier as the member/property name and resolve the "dot" expression to get the value.
const sandwichesIWantToEatResult = { CHEESE_STEAK: SANDWICHES.CHEESE_STEAK, SLOPPY_JOE: SANDWICHES.SLOPPY_JOE, IDENTIFIER: IDENTIFIER };
Jonathan Bond-Caron wrote:
On Tue Sep 20 03:38 PM, Bob Myers wrote:
People in the real world continue to wonder why they can't pick/destructure from objects into objects, instead of just variables.
Yeah, StackOverflow is hit pretty often with people asking how to do that.
Seems like allowing to "dot" into another identifier could work: tc39.github.io/ecma262/#prod-CoverInitializedName
CoverInitializedName[Yield]: IdentifierReference[?Yield] Initializer[+In, ?Yield] IdentifierReference[?Yield] . IdentifierName
I don't think that's the right grammar rule, but yes, I'd love to see this:
const IDENTIFIER = 1; const sandwichesIWantToEat = { SANDWICHES.CHEESE_STEAK, SANDWICHES.SLOPPY_JOE, IDENTIFIER };
Use the RHS identifier as the member/property name and resolve the "dot" expression to get the value.
const sandwichesIWantToEatResult = { CHEESE_STEAK: SANDWICHES.CHEESE_STEAK, SLOPPY_JOE: SANDWICHES.SLOPPY_JOE, IDENTIFIER: IDENTIFIER };
This simplification in object destructuring and shorthand property intialisers should be easy to add to the language and rather simple to understand. There are no grammar ambiguities, and no completely new productions, but I believe it would help a great deal. Yes, one would still have to repeat the name of the object to/from which the properties to assign/take, but that's usually rather short so it's not a large burden.
I could create a proposal repo, who would like to help?
kind , Bergi
On Wed, Sep 21, 2016 at 3:39 PM, Bob Myers <rtm at gol.com> wrote:
Some people on a web site are curious to know if they can reduce 2 lines of JS code to 1 line
Right, some people wanted to reduce 2 lines of JS code [...]
That is not historically how JS got destructuring.
But it's a tiny bit more than mere syntactic sugar. Writing
obj.{p1, p2}
is just sugar for writing{p1: obj.p1, p2: obj.p2}
. But I can also writeobj.{p1: q1, p2 = 100}
[...]
In destructuring, {p1: q1} = obj
means q1 = obj.p1
. So, do you mean for
this to say {q1: obj.p1}
? Or perhaps {p1: obj.q1}
; but then why is it
inconsistent with destructuring?
You mention deep picking, but I haven't seen what that would actually look like. (Destructuring is not great for deep picking, so I'm not sure how the new syntax requires negative effort to learn because it's all existing parts, and yet it will also solve this problem.)
You mention computed property names. How does that work? In obj.{[p1]}
,
does p1
mean the variable p1
or the property obj.p1
? Assuming the
former, is there anyplace else in the language where adding []
affects
what an identifier refers to (i.e. scoping)?
How is all of this not a ton of new things for JS developers to learn?
I totally agree with Matthew Robb's comment in a separate response that
this proposal is not imposing a new cognitive burden; it's REMOVING the cognitive burden of wondering why our handy deconstructing syntax cannot be used to pick properties into objects.
This argument is hopeless, and I find it a little weird because a much stronger argument is available to you. You can just say, yes, there is some minor complexity cost here, but it's definitely worth it because of very common use cases X, Y, and Z.
The argument you've chosen instead won't go. None of these proposals could actually be standardized in a way that reduces the total size of the spec. They won't make "JavaScript: The Definitive Guide" a shorter book, either. They all require extra words to explain, whether to implementors or to JS devs. New syntax does not make things simpler, in the same way that up is not down and black is not white.
Cognitive burden is a real phenomenon, where people have to spend time puzzling out unfamiliar ideas and it's pure overhead for them vs. their actual goal. You can't just turn the meaning of the term upside down and be done. It actually means something.
(Also, both you and Matthew seem to think an unlikely point can be carried with the help of allcaps. Admittedly they are very judiciously applied allcaps, relative to the internet at large, but I still don't think this ever works!)
This is a creative idea. I'd rather see it in the language than not. But still half a loaf.
Minor nit: I don't see how this is a "simplification in object destructuring". It has nothing to do with destructuring, right?
I assume that this would work as expected?
{SANDWICHES[mySandwichName]}
to produce
{[mySandwichName]: SANDWICHES[mySandwichName]}
It's a bit sad that I don't see how to get to renaming and defaults with this syntax.
Bob
With regard to deep picking, with standard destructuring I write
const {foo: {bar: { qux }}} = obj;
which means
const qux = obj.foo.bar.qux;
In the proposed "pick/destructure into an object" syntax, I would use identical destructuring syntax
obj.{foo: {bar: {qux}}}
which would turn into {qux: obj.foo.bar.qux}
.
We are simply using identical destructuring syntax to destructure into an object instead of a variable.
Sorry for using ALL CAPS. I will not do that any more. You're right: this proposal DOES (oops, I meant does) increase the size of the spec. Is that the new criteria, that no proposal may increase the size of the spec? Then we might as well freeze JS right now.
The "extra words" you refer to are nothing more this:
You can also put deconstructing syntax following a dot after an object.
This will result in a new object with properties determined by the deconstructor.
None of the ES6 enhancements made "JavaScript: The Definitive Guide" a shorter book. Are you saying that means they should not have been included in the spec?
Bob
Bob Myers wrote:
This is a creative idea. I'd rather see it in the language than not. But still half a loaf.
Yeah, it's not the whole thing, but I believe it's something the TC could agree on before moving further.
Minor nit: I don't see how this is a "simplification in object destructuring". It has nothing to do with destructuring, right?
For consistency and ease of understanding I would apply the same pattern for destructuring cases, i.e.
({o1.x, o1[y]} = o2);
desugars to
({x: o1.x, [y]: o1[y]} = o2); // but `y` being evaluated only once
This would help a great deal where the object on which the properties should be assigned already exists, e.g. in a constructor:
constructor(options) {
({this.propA, this.propB, this.optOne} = options);
}
to provide some kind of "selective Object.assign
".
It's a bit sad that I don't see how to get to renaming and defaults with this syntax.
Renaming has always been part of the language:
o2 = {y: o1.x} // o2.y = o1.x
({y: o2.z} = o1); // o2.z = o1.y
and with destructuring we'd also get defaults.
First I'd like to state that I don't mean to suggest that this is "simple", in practical terms, as either a spec change or in application of the feature in day-to-day code.
On Thu, Sep 22, 2016 at 11:24 AM, Jason Orendorff <jason.orendorff at gmail.com
wrote:
I get that to you, that's all it is. But I have trouble seeing how that is true in any objective sense. In the spec
The point I was trying to make is that "to me" is exactly the perspective
I was hoping to share as people writing JavaScript don't have an intimate
understanding of the spec more often than not. In teaching people who spend
most of their time writing JavaScript where each line starts with $(...)
I get more questions about things that look like they should work some way
in one place because it actually works that way in another than anything
else.
I don't seek to trivialize either side of the argument but with syntax in particular I would hope that complexity additions be back-loaded into the spec rather than front-loaded onto users.
I apologize FOR MY USE OF CAPS TO EXPRESS emphasis. It's a bad habit :P
- Matthew Robb
On Thu, Sep 22, 2016 at 1:14 PM, Bob Myers <rtm at gol.com> wrote:
In the proposed "pick/destructure into an object" syntax, I would use identical destructuring syntax
obj.{foo: {bar: {qux}}}
which would turn into
{qux: obj.foo.bar.qux}
.We are simply using identical destructuring syntax to destructure into an object instead of a variable.
I'm sorry, Bob, the link in your original post in this thread has a link to the proposal; it's perfectly clear on this, and I should have gone back to it.
OK, I think I understand the proposal now. A few comments:
-
The gist says:
The syntax of the Assignment Pattern is identical.
I don't think this is right; I think you want all the right-hand sides of all the properties in the tree to match PropertyName. In AssignmentPattern, those can be any LeftHandSideExpression:
{text: f().q[13].name} = obj;
But that wouldn't make sense in pick notation:
obj.{text: f().q[13].name} // ???
-
I mentioned scoping earlier. What I mean is, in expressions like
obj.{[p]: p}
the two identifiers
p
mean different things (the firstp
is a variable name, the secondp
a property name), which is new and subtle. The same thing happens inobj.{p = p}
, except the other way round. Destructuring does just a touch of this in the shorthand case, but I never found that confusing. This is. -
I wonder if this can be made more symmetrical -- as it stands, it allows you to flatten a complex object:
// from a tree of 3 objects to one let rect = corners.{ p0: {x: left, y: top}, p1: {x: right, y: bottom} };
The reverse would be to add structure to a flat object. There's no way to do that, right?
// turn the 1 object back into a tree of 3? let corners = rect.{ ??? };
Likewise, it seems you can turn an array into a plain object, but not the other way around?
let point = coords.[x, y]; // {x: coords[0], y: coords[1]} let coords = point.{???}; // [point.x, point.y]
Honestly even the examples that do work seem ugly to me. I think pretty much all the value in the proposal is in the simplest case,
obj.{identifier, ...}
.
On Thu, Sep 22, 2016 at 1:14 PM, Bob Myers <rtm at gol.com> wrote:
Sorry for using ALL CAPS. I will not do that any more. You're right: this proposal DOES (oops, I meant does) increase the size of the spec. Is that the new criteria, that no proposal may increase the size of the spec?
This conversation has been kind of strange for me.
JO: new cognitive burden has to be justified BM: this isn't new cognitive burden, it's removing cognitive burden JO: that makes no sense BM: are you saying we need to freeze js forever?
...Well, no. Obviously.
But new cognitive burden has to be justified.
TC39 didn't accept array comprehensions. They were generally well-liked, but they didn't introduce any new capabilities and it was decided they didn't pull their weight. That's the bar any new proposal has to get over.
Then we might as well freeze JS right now.
The "extra words" you refer to are nothing more this:
You can also put deconstructing syntax following a dot after an object. This will result in a new object with properties determined by the deconstructor.
I think your gist is more a serious attempt at a reasonable explanation.
Don't trivialize it -- people who don't get the memo will have to search
StackOverflow for .{
.
I'd like to reframe the discussion a bit, starting with a definition of picking.
-
"Picking" means to create a new object ("pick target") containing a subset of specified properties and their values drawn from another object (or possibly several other objects) ("pick source").
-
"Renaming" means that a property is given a different name in the target than the source.
-
"Defaults" means that a property missing in the source is given some specified value.
-
"Deep picking" means that a property to be put on the target may be drawn from within a nested object on the source.
So all of the following things are picking:
{p1: o.p1, p2: o.p2}
const {p1, p2} = o;
{p1, p2}
(({p1, p2}) => ({p1, p2}))(o)
_.pick(o, 'p1', 'p2')
I will take it as axiomatic that most JS programs contain a reasonable amount of picking. The question here is whether to add core language features to support picking. The normal criteria for new language features apply. Is it something that cannot be done in userland code, or is it mere syntactic sugar? In the latter case, how sweet is the sugar? Depending on the specifics of the proposed syntax, to what extent does it improve readability, compactness, and program correctness? Do these considerations balance against the "cognitive burden"? Are there optimization considerations? Is it typeable? Are there parseability concerns?
Picking is merely syntactic sugar. So we need to examine the current
picking approaches given above and reason about them in the context of
these criteria. We'll start with {p1: o.p1, p2: o.p2}
. It's unsatisfying
that o
, p1
, and p2
are all repeated twice in this construct. Each
such repetition has a potential for error. Because of the duplication, the
overall construct seems longer than ideal. It supports renaming and deep
picking, in the form of {newp1: o.p1, p2: o.x.p2}
. But it does not
support defaults, unless one wants to write {p1: o.p1, p2: 'p2' in o ? o.p2 : "default"}
. In all cases it is perfectly typeable.
I often see the second alternative of const {p1, p2} = o; target = {p1, p2};
. But this is also intellectually unsatisfying, because again the p1
and p2
must be repeated, and the local scope is polluted with unnecessary
new variables. However, it does support defaults by virtue of defaults in
deconstructing assignments.
The third alternative of (({p1, p2}) => ({p1, p2}))(o)
seems unduly
awkward.
A library routine such as _.pick
is the solution many people point to,
but it also seems unsatisfying. It is not easily extensible to support
defaults or renaming. It is also hard to implement type safety.
So at the end of the day it boils down to a subjective judgment about the tradeoff between remedying these deficiencies and the cognitive burden and complexity of a new language feature that attempts to address them. Of course, this tradeoff depends on the specifics of some proposed new syntax, and to what extent it is "obvious", or "natural", or leverages existing syntax features in an intuitive way, or in general has a smaller perceptual footprint.
If your judgment is that any incremental cognitive burden whatever could not possibly justify any new syntax, because the deficiencies are just not that severe, then you are not in favor of this proposal and that's fine. We'll call this position A, the "just say no" position. The converse is position B, the "I want picking in the language" position.
If you take position B, and thus are willing to consider new syntax, but want to absolutely minimize the footprint, acknowledging that all the design goals might not be met, then we can adopt @Bergi's proposal to simply extend existing shorthand property notation, as follows:
{o.p1, o.p2}
We will call this position B1, "qualified shorthand properties". It's
definitely an improvement! But it still requires repeating o
, and also
doesn't give us defaults or renaming.
But if we want a "real" picking construct, then assuming it's clean enough to overcome the cognitive burden-vs-benefit hurdle, almost by definition it must involve (a) an object to pick from; (b) a specification for which properties to pick (the "picker"); and (c) a mechanism to indicate that picking is to occur.
One obvious mechanism to indicate that picking is to occur is to introduce a picking operator. We will call this position B2, the "I want a picking operator" position.
In that case consider the following:
-
We already have the dot notation, which takes a single property from an object and returns its value as a scalar. The dot notation currently only takes an identifier--the key--on its right hand side. Anything else is a syntax error. In other words, the dot
.
can already be considered a particular kind of pick operator, limited to picking scalar values. -
We already have the deconstructing pattern syntax, which is a way of specifying a set of properties (including computed properties) to extract from an object, along with ways to rename and assign default values to those properties. In other words, it is a kind of "picker". But beyond its use in parameter deconstruction, it is currently used only in deconstructing assignment. The extracted values are exclusively used as values to assign to variables.
That is what is behind the proposal to use the existing dot as the pick
operator. That is why this proposal is called "extended dot notation". And
we use an AssignmentPattern as a "picker". We combine these two familiar
notions into the syntax o.{p1, p2}
. Voila, we have picking, defaults,
deep picking, and renaming by virtue of those features already being in the
deconstructing pattern syntax.
We will call this position B2a, "I want to use the dot for the picking operator".
The gist is:
You can put a deconstructing pattern after a dot, and it will create a new object with the deconstructed names/values as properties.
If the dot is considered too sacred to repurpose, or if we want something
that is more visible than a dot, then we could substitute the token pick
.
We will call this position B2b, "I want to use pick
for the picking
operator". The syntax would be
o pick {p1, p2}
As far as I can see, this is entirely backward compatible and parseable.
However, there is another possible mechanism to indicate that picking is to
occur. That is to place new syntax inside the existing {}
object
literal delimiters. The conceptual notion is something along the lines of
{ PICK-P1-AND-P2-FROM-O }
. In other words, optimize the {p1: o.p1, p2: o.p2}
syntax. The proposed way to do this is to put something that looks
exactly like a deconstructing assignment inside the curly brackets:
{ {p1, p2} = o }
These nested curly brackets may put off some folks. However, this approach does have the advantage of exactly re-using existing deconstructing assignment syntax. The gist is:
A deconstructing assignment can be put inside curly brackets, and the resulting names and values are put into the object as properties
This particular solution has the advantage that you can deconstruct from multiple objects into a new object, as in
{ {p1, p2} = o, {q1, q2} = o2 }
We will call this position B3, "I want to pick by putting a deconstructing assignment inside curlies".
For your convenience, here is a summary of the positions:
- A: just say no
- B: I want picking in the language
- B1: I want qualified shorthand properties
- B2: I want a picking operator
- B2a, *I want to use the dot for the picking operator
- B2b: I want to use
pick
for the picking operator - B3: I want to pick by putting a deconstructing assignment inside curlies
By the way, sorry for getting drawn into the "reduced cognitive load" discussion. Yes, this proposal does involve additional cognitive load. The right question is how to minimize it, and to what extent it is justified.
-- Bob
I'll note that the type checking part will eventually get fixed in TypeScript, at least.
Oh, and I've already suggested another, more specific, pick
syntax (that
operation is only a shorthand for x => x.property
) ,but it ultimately got
rejected since no one could really agree to anything different other than the current state was workable, but not optimal. You may appreciate reading the entire conversation, as it's pretty informative and is related to this.
Here's the specific comment proposing that idea: tc39/proposal-bind-operator#24
Here's the issue where most the related discussion took place: tc39/proposal-bind-operator#26
On Wed, Sep 7, 2016, 04:10 Kris Siegel <krissiegel at gmail.com> wrote:
I'm just saying those references still exist, and ignoring them will lead you into more problems than this would solve.
Not suggesting anything of the sort only that your workaround adds additional variables with possible object references and a more native implementation could work around that. Nothing more.
And this isn't C: unless you're using those references, JS engines will clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
Hmm this is interesting. It's been a while since I last memory profiled a web application but the last time if I kept any references to, say, DOM elements in any variables even if I was no longer using them they would still be eating up the memory causing memory leaks. Are you saying this is no longer the case? You don't need to necessarily get rid of all references to, say, a DOM element before the garbage collector will sweep it up?
If the DOM element is not live and either is not referenced, or referenced only by things that are marked for collection, the GC will mark it (assuming they are only referenced by it). Then after a while, it's swept away with the rest of the garbage.
(Technically, this is an oversimplification, but it's a complex topic. Also, this doesn't apply to really old IE, which uses reference counting instead.)
I'm more curious than anything else; it appears my understanding here might be wrong so I want to dig into it more.
You do, but extra syntax for pick
makes 0 difference in this area. My
point is that the new references in between are created and retained
regardless of how you create them.
Oh, and I've already suggested another, more specific,
pick
syntaxGood read. Interesting. Honestly I'm more of a fan of introducing functions if possible to handle much of this instead of new syntax though I don't know if that is necessarily supported by others and that proposal goes a bit before what I was originally thinking.
I agree, too. And for individual properties, it is helpful, but this
language isn't quite as functional as people want to think. Also, it's not
like the alternatives are much larger: x => x.property
(and for the
original proposal's shorthand: (...xs) => console.log(...xs)
).
I'm just saying those references still exist, and ignoring them will lead you into more problems than this would solve. First, my above workaround would likely be the desugaring in V8. You could optimize it by creating the actual object and assigning the properties as they come from the object, but that's also a valid optimization for engines to make today with my workaround. Also, most other optimizations are equally permissible with both syntax and my equivalent workaround.
And this isn't C: unless you're using those references, JS engines will clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
As you can see, I'm generally against this.
Isiah's workaround works but has the unfortunate side affect of copying
values / increasing reference counts to objects. I'd love to see a built in solution.
You'd have to do that anyways, even if it's entirely internal. How do you think you would do it outside of copying the values?
I'm just saying those references still exist, and ignoring them will lead
you into more problems than this would solve.
Not suggesting anything of the sort only that your workaround adds additional variables with possible object references and a more native implementation could work around that. Nothing more.
And this isn't C: unless you're using those references, JS engines will
clean them up automatically, anyways, because of their garbage collection. My workaround produces 0 more required object references than what the engine needs.
Hmm this is interesting. It's been a while since I last memory profiled a web application but the last time if I kept any references to, say, DOM elements in any variables even if I was no longer using them they would still be eating up the memory causing memory leaks. Are you saying this is no longer the case? You don't need to necessarily get rid of all references to, say, a DOM element before the garbage collector will sweep it up?
I'm more curious than anything else; it appears my understanding here might be wrong so I want to dig into it more.
Oh, and I've already suggested another, more specific,
pick
syntax
Good read. Interesting. Honestly I'm more of a fan of introducing functions if possible to handle much of this instead of new syntax though I don't know if that is necessarily supported by others and that proposal goes a bit before what I was originally thinking.
I just find the syntax confusing
It combines two syntaxes we know and love: (1) dot notation o.b
and (2)
deconstructing notation {a} = o
, into o.{a}
.
Maybe some have problems with the dot in a.{o}
being too inconspicuous.
At the cost of using up a precious special character, we could go with o # {a}
instead. Actually I think o pick {a}
is syntactically unambiguous.
I do like the idea of building a way of taking a portion of an object out
of one object and into another
Good, many people seem to agree.
Why couldn't this be an Object.pick()
Personally, passing around properties as strings grates on me. Then there's
the issue of typability, although Isiah has pointed out a useful TypeScript
proposal that does deal with that. Within the _.pick
paradigm, it's hard
to envision a concise syntax for defaults and renaming, which the
deconstructing-like syntax gives us for free.
It could even be expanded to handle deep nesting
Just curious, what sort of interface to your _.pick
-like thing do you
have in mind>? Will it support defaults and renaming?
By the way, I was looking at Elm and found it supports one advanced feature
of extended dot notation, which is the unary dot as function, as in
arr.map(.p)
.
Bob
I just find the syntax confusing
It combines two syntaxes we know and love: (1) dot notation o.b
and (2)
deconstructing notation {a} = o
, into o.{a}
.
Maybe some have problems with the dot in a.{o}
being too inconspicuous.
At the cost of using up a precious special character, we could go with o # {a}
instead. Actually I think o pick {a}
is syntactically unambiguous.
I do like the idea of building a way of taking a portion of an object out
of one object and into another
Good, many people seem to agree.
Why couldn't this be an Object.pick()
Personally, passing around properties as strings grates on me. Then there's
the issue of typability, although Isiah has pointed out a useful TypeScript
proposal that does deal with that. Within the _.pick
paradigm, it's hard
to envision a concise syntax for defaults and renaming, which the
deconstructing-like syntax gives us for free.
It could even be expanded to handle deep nesting
Just curious, what sort of interface to your _.pick
-like thing do you
have in mind>? Will it support defaults and renaming?
By the way, I was looking at Elm and found it supports one advanced feature
of extended dot notation, which is the unary dot as function, as in
arr.map(.p)
.
Bob
Since some people seem to find the proposed pick notation o.{p1, p2}
, (to
create an object composed of the p1
and p2
properties from o
)
confusing, I am making an alternative proposal which hews more closely to
existing deconstructing assignment.
The definition of the new syntax is:
If a deconstructing assignment is wrapped in curly brackets
{}
, the
result is a new object where the deconstructed names and values become property names and values.
The above case would be written as:
{ {p1, p2} = o }
and would result in
{ p1: o.p1, p2: o.p2 }
AFAICT the above syntax should be uniquely parseable. The advantage of this syntax is the minimal cognitive footprint: if you know how to deconstruct an object into variables using deconstructing assignment, then you can deconstruct an object into properties on a new object with nothing more than an extra set of surrounding curlies.
I will be adding this alternative syntax to the repo here rtm/js-pick-notation and here rtm/js-pick-notation/blob/master/minimal/spec.md in
the near future.
Bob
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20171010/d1804e32/attachment
Extended dot notation is a proposal for a new syntax for picking properties into objects. The canonical example is
let o1 = { a: 1, b: 2}; let o2 = o1.{a}; // {a: 1}
You may think of this as deconstructing objects into objects, rather than variables.
Syntactically, the extended dot notation is the dot followed by something in curly braces. This avoids eating up another precious special symbol, yet is unambiguous since at present only an identifier can follow a dot.
I've put together a short introduction at rtm/js-pick-notation/blob/master/docs/intro.md. This repository also contains a full spec at rtm/js-pick-notation/blob/master/js-pick-notation.md. The spec includes a section on motivation, which in a nutshell is expressiveness and brevity. The repo also features a proof-of-concept implementation using sweet.js.
A variant of the proposal uses
#
instead of.
, the pros and cons of which are discussed in the spec.I won't repeat details from the introduction here, but there are more features of interest, including picking into arrays, pickers as functions, etc.
I'm looking for good feedback as well as a champion for this feature.
-- Bob Myers