Bob Myers (2016-09-24T20:47:03.000Z)
rtm at gol.com (2016-09-24T20:52:18.706Z)
I'd like to reframe the discussion a bit, starting with a definition of picking. 1. "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"). 2. "Renaming" means that a property is given a different name in the target than the source. 3. "Defaults" means that a property missing in the source is given some specified value. 4. "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: 1. 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. 2. 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