Functional Destructuring: Destructure via function calls using new syntax

Background

It's typical to run into a situation where you want to get several values from an object or array. Rather than breaking each value retrieval up separately, destructuring solved that issue and allowed developers to start retrieving all desired values in a single declaration.

Traditional destructuring looks something like this:

Objects

const obj = { a: 1, b: 2, c: 3 };
const { a, b, c, d = b + c } = obj;

console.log(a, b, c, d) // -> 1, 2, 3, 5

Arrays

const arr = [1, 2, 3];
const [a, b, c, d = b + c] = arr;

console.log(a, b, c, d) // -> 1, 2, 3, 5

Explanation of the problem

However, there is no equivalent method for destructuring functional return values from a single value. For example, suppose I am developing a chess set. I might need several pieces of data about one chess piece in particular, and some of that data—rather than existing as properties on the node itself—is computed by passing the node into a function like this:

const chessPiece = document.querySelector('.chess-piece');

const key = getPieceKey(chessPiece),
      alg = getElemAlg(chessPiece),
      pos = getElemPos(chessPiece),
      cid = `${key}${alg}`;

Proposal

I'm not suggesting a third destructuring syntax separate from the existing Object/Array destructuring syntaxes. Instead, I am proposing a new syntax that would work within either. This is what that could look like, similar to the computed property keys syntax.

Using the same example from above, the new syntax could look something like this:

const chessPiece = document.querySelector('.chess-piece');

const {
  [getPieceKey]: key,
  [getElemAlg]: alg,
  [getElemPos]: pos,
  cid = `${key}${alg}`
} = chessPiece;

Candidly, where I thought this might get trickier was Array destructuring, as the typical nested array destructuring syntax is nearly identical to this (see the next example below). However, if we include the same computed property keys-style syntax mentioned above, we avoid any reserved syntax conflict since the value is only being stored into a single variable name, similar to other destructured array values. Admittedly, this deviates more from traditional Array syntax, but it only adds one layer which only applies to destructuring, adds great value, and doesn't break any existing syntax.

const doubleFirstValue = ([firstValue]) => firstValue * 2;
const arr = [1, [2], [[3]]];
const [a, [b], [[c]], [doubleFirstValue]: aDoubled] = arr;

console.log(a, b, c, aDoubled) // -> 1, 2, 3, 2

The above example demonstrates both Array destructuring and Functional destructuring. Using this syntax likely wouldn't be as useful for Array destructuring compared to Object destructuring, but the example helps to illustrate that this would not cause any conflicts or be a breaking change to the existing syntaxes in place.

The same example could be used with Object destructuring syntax—which works more flexibly though less concisely—if a developer wanted to destructure without respect to the original insertion order of the array:

const doubleFirstValue = ([firstValue]) => firstValue * 2;
const arr = [1, [2], [[3]]];
const {
  0: a,
  [doubleFirstValue]: aDoubled,
  1: { 0: b },
  2: { 0: { 0: c } },
} = arr;

console.log(a, b, c, aDoubled) // -> 1, 2, 3, 2

Again, not a typical use case, but this works to demonstrate how flexible this could work for functionally destructuring from single values, arrays, or objects.

It would likely be more common to see examples like this crudely simple one:

const base = 5;

const {
  [d => d * 2]: baseTimes2,
  [d => d * 5]: baseTimes5,
  [d => d * 10]: baseTimes10,
} = arr;

console.log(base, baseTimes2, baseTimes5, baseTimes10) // -> 5, 10, 25, 50

So far, this syntax seems to work across all cases I've considered, but I'd like to ask this community what a better syntax might be for this. What do you all think?

Workaround-ish…?

The closest I can seem to get this today without the proposed functional destructuring is to employ a helper function on the right-hand side of the destructuring statement, like this:

const helperFn = (value, functions) => (
  functions.map((fn, i) => (
    typeof fn === 'function' ? fn(value) : value[i]
  ))
);
const chessPiece = document.querySelector('.chess-piece');

const [key, alg, pos, cid = `${key}${alg}`] = helperFn(chessPiece, [getPieceKey, getElemAlg, getElemPos]);

However, using a helper function this way may prove less coherent and more complicated than using separate declarations, so I don't think it is a proper workaround. I've certainly come across this need enough times to warrant my attention. Adding a new syntax like the one I've suggested above could significantly help improve the overall destructuring DX, and I would love to discuss it further.

Open to any constructive feedback here. Thanks!

Actually the examples you posted already are valid syntax, precisely computed property keys. We cannot change their semantics.

I disagree. It sounds like you understand "functional" to mean point-free, which is considered to be less developer-friendly than point-full code. Of your examples, I think that

const chessPiece = document.querySelector('.chess-piece'),
      key = getPieceKey(chessPiece),
      alg = getElemAlg(chessPiece),
      pos = getElemPos(chessPiece),
      cid = `${key}${alg}`;

is by far the cleanest and most easily understood. If you really wanted to get rid of the repeated chessPiece variable, you can already do that with a helper function like the one you showed:

const [key, alg, pos] = helperFn(getPieceKey, getElemAlg, getElemPos)(document.querySelector('.chess-piece')),
      cid = `${key}${alg}`;

I don't think this needs new syntax.

1 Like

I'd like to highlight one last example, as it gets pretty close to what this proposal seeks to accomplish, with one fatal flaw…

const apiResponse = [
  {
    title: 'A single day in Paris',
    subtitle: 'Let\'s talk Eiffel…',
    content: 'Lorem ipsum yada yada',
    category: 'travel',
    date: '7/8/2022 19:05:46 GMT-0400',
  },
];

const buildSlug = ({ title }) => title.toLowerCase().replaceAll(' ', '-');
const getCharCount = ({ content }) => string.length;
const getWordCount = ({ content }) => string.split(' ').length;
const getDaysSince = ({ date }) => (new Date() - new Date(date)) / 864e5;

for (const article of apiResponse) {
  const {
    title,
    subtitle,
    content,
    category,
    slug = buildSlug(article),
    date = getDaysSince(article),
    wordCount = getWordCount(article),
    charCount = getCharCount(article),
  } = article;
}

This produces these destructured values:

{
  title: 'A single day in Paris',
  subtitle: "Let's talk Eiffel…",
  content: 'Lorem ipsum yada yada',
  category: 'travel',
  slug: 'a-single-day-in-paris',
  date: '7/8/2022 19:05:46 GMT-0400',
  wordCount: 4,
  charCount: 21
}

At first glance, this appears to work almost identically to the proposed functionality, but one crucial flaw here is that it utilizes default values, meaning that if you want to functionally destructure to a variable name that also exists as a property on the object you are destructuring from, the original value on the source object takes priority.

This flaw can be seen in the above example if you inspect the output for date. The getDaysSince function should be storing the number of days since that date, but instead, the original datestring from the object is stored. Sure… in this example, one should probably choose a different and more specific variable name to use the default on, but using this method should be avoided as it will always pose the risk of the data you're reading overriding the variable value, especially if you're unaware of the exact structure of the data.

Instead, this would be preferred:

const apiResponse = [
  {
    title: 'A single day in Paris',
    subtitle: 'Let\'s talk Eiffel…',
    content: 'Lorem ipsum yada yada',
    category: 'travel',
    date: '7/8/2022 19:05:46 GMT-0400',
  },
];

const buildSlug = ({ title }) => title.toLowerCase().replaceAll(' ', '-');
const getCharCount = ({ content }) => string.length;
const getWordCount = ({ content }) => string.split(' ').length;
const getDaysSince = ({ date }) => (new Date() - new Date(date)) / 864e5;

for (const article of apiResponse) {
  const {
    title,
    subtitle,
    content,
    category,
    [buildSlug]: slug,
    [getDaysSince]: date,
    [getWordCount]: wordCount,
    [getCharCount]: charCount,
  } = article;
}

@bergus Thanks for sharing your thoughts. I was unaware that computed property keys were supported in destructuring. TIL… :sweat_smile:

All the same, I'd like to continue discussing as I see tremendous value in being able to quickly functionally destructure, especially for examples like the last one I just added in my previous comment.

Open to other ideas a different non-problematic syntax.

Yeah I was going to complain about that abuse :-) Just write

for (const article of apiResponse) {
  const {
    title,
    subtitle,
    content,
    category,
  } = article,
  slug = buildSlug(article),
  date = getDaysSince(article),
  wordCount = getWordCount(article),
  charCount = getCharCount(article);
}

instead!

I maintain my disapproval. Destructuring is not well-suited for this, since you want to pass the entire structure into each of those functions, not an individual part. Really this is just a glorified loop, as your helperFn demonstrates. I'd be open to standardising that helper functionality though - it's the well-known ap method of applicative functors. Providing it as an array method could make this look like

const [slug, date, wordCount, charCount] = [buildSlug, getDaysSince, getWordCount, getCharCount].ap(article);
// same as
const [slug, date, wordCount, charCount] = [buildSlug(article), getDaysSince(article), getWordCount(article), getCharCount(article)]

If however we were to pass only a part of the destructured object/array into the function, I could see this fit well into the concept. One might consider a syntax like the following:

const buildSlug = (string) => string.toLowerCase().replaceAll(' ', '-');
const getCharCount = (string) => string.length;
const getWordCount = (string) => string.split(' ').length;
const getDaysSince = (date) => (new Date() - new Date(date)) / 864e5;

for (const article of apiResponse) {
  const {
    title,
    subtitle: subTitle,
    content,
    category,
    title: buildSlug(slug),
    date: getDaysSince(days),
    content: getWordCount(wordCount),
    content: getCharCount(charCount),
    title: getCharCount(titleCharCount),
  } = article;
}

which would work like

const title = article.title,
      subTitle = article.subtitle,
      content = article.content,
      category = article.content,
      slug = buildSlug(article.title),
      days = getDaysSince(article.date),
      wordCount = getWordCount(article.content),
      charCount = getCharCount(article.content),
      titleCharCount = getCharCount(article.title);

Similarly for array patterns, we could make

const [fn1(a), fn2(b)] = arr;
// same as
const a = fn1(arr[0]), b = fn2(arr[1]);
1 Like

@bergus Thanks for the constructive feedback! I really like both suggestions you made.

Array.prototype.ap — array method

I really like this idea and think this hypothetical ap array method you mentioned (something along those lines) would be extremely useful for destructuring as well as other uses, essentially an inverted Array.prototype.map method.

Destructuring syntax

content: getWordCount(wordCount)

I also really liked the direction of this destructuring syntax. I had two thoughts I'd still like to work through—

  • Having the new variable name/alias inside the function could be very confusing for some, as it's identical to how most functions pass arg(s) functions. It might be more understandable to invert the function and variable name, like this:
    content: wordCount(getWordCount)
    
  • This method requires to destructure from a single property (content: ...), but some properties may need to be built off of multiple properties, which in the originally suggested syntax would have looked like this (below), simply for the sake of example, not backtracking here: :grin:
    [({ slug, date }) => /* returned data here */]: aliasName
    

This destructuring syntax is starting to become similar to a previous idea we've talked about here - in that thread a different syntax was being proposed, but the fundamental idea is the same:

const buildSlug = (string) => string.toLowerCase().replaceAll(' ', '-');
const getCharCount = (string) => string.length;
const getWordCount = (string) => string.split(' ').length;
const getDaysSince = (date) => (new Date() - new Date(date)) / 864e5;

for (const article of apiResponse) {
  const {
    title,
    subtitle: subTitle,
    content,
    category,
    title: slug via buildSlug,
    date: days via getDaysSince,
    content: wordCount via getWordCount,
    content: charCount via getCharCount,
    title: titleCharCount via getCharCount,
  } = article;
}

Another option to solve the original problem would be this extensions proposal. With this proposal, you can define arbitrary getters and use them on objects you don't own. If I understand it correctly, this should allow you to use third-party getters as you destructure, so you could do something like this:

for (const article of apiResponse) {
  const {
    title,
    subtitle,
    content,
    category,
    ::buildSlug: slug,
    ::getDaysSince: date,
    ::getWordCount: wordCount,
    ::getCharCount: charCount,
  } = article;
}

This proposal doesn't actually have examples of how to destructure using getter-extensions, so that was just my best guess at how the syntax would be. I also don't have a ton of hope for this proposal's future, as I've seen a couple of other proposals come out with the intent to replace this one. But, you never know - we'll see how things pan out.

3 Likes

@theScottyJam Thanks! I hadn't seen either of those, and they definitely work toward something similar. I really like the implications of that via approach, and I think we can even take that one step further— it would be useful for this use case (destructuring specifically), to be able to use the via keyword outside the value, alongside the property name which also wouldn't conflict with any existing syntax AFAIK.

The advantages here are that doing it this way— (a) works more closely to the existing syntax for nested destructuring, which in turn (b) supports via for both the top-level object and individual properties.

const apiResponse = [
  {
    title: 'A single day in Paris',
    subtitle: 'Let\'s talk Eiffel…',
    content: 'Lorem ipsum yada yada',
    category: 'travel',
    date: '7/8/2022 19:05:46 GMT-0400',
    likes: 22,
    comments: 5,
    views: 364,
  },
];

const buildSlug = (string) => string.toLowerCase().replaceAll(' ', '-');
const getCharCount = (string) => string.length;
const getWordCount = (string) => string.split(' ').length;
const getDaysSince = (date) => (new Date() - new Date(date)) / 864e5;
const countEngagements = ({ likes, comments, views }) => {
  return likes + comments + views;
};

for (const article of apiResponse) {
  const {
    /* standard destructuring */
    title,
    subtitle: subTitle,
    content,
    category,

    /* standard destructuring w/ aliasing */
    likes: likeCount,
    comments: commentCount,
    views: viewCount,

    /* [TOP] destructuring from the full object for
       access to all properties in `via`-passed function */
    via countEngagements: engagementCount,

    /* [NESTED] destructuring from an individual property */
    title: { via buildSlug: slug },
    date: { via getDaysSince: days },
    content: { via getWordCount: wordCount },
    content: { via getCharCount: charCount },
    title: { via getCharCount: titleCharCount },

    /* [NESTED-SUB] destructuring from an individual sub-property */
    title: { 0: { via String.prototype.toUpperCase.apply: dropCap } },
  } = article;
}

The via keyword working at the key-level in destructuring opens the door to easily using it within top-level or nested destructuring. Many new properties are derived from several other existing ones, so it'd be nice to have a straightforward way of passing the full object, as the above example demonstrates.

I'd also argue that this syntax is more coherent alongside the existing method(s) for Object destructuring, compared to using via in the value (example below), which wouldn't support top-level functional destructuring:

{ content: wordCount via getWordCount }

^ This only supports nested destructuring for a single property, whereas the previously suggested syntax supports destructuring using the full object as well as nested properties.

Even in that case, typically in Object destructuring, the last name listed is the name of the new variable (prop: { subprop: alias }) which would work more closely to the example previously suggested (prop: { via fn: alias }) and would retain support for nested sub-properties as well (see the final example below)

const {
  /* 1 - [TOP] destructuring from the full object for
     access to all properties in `via`-passed function */
  via countEngagements: engagementCount,

  /* 2 - [NESTED] destructuring from an individual property */
  title: { via buildSlug: slug },

  /* 3 - [NESTED-SUB] destructuring from an individual sub-property */
  title: { 0: { via String.prototype.toUpperCase.apply: dropCap } },
}

This example shows examples from the full spectrum of functional destructuring using via:

  1. The first usage demonstrates destructuring for the full object
  2. The second usage demonstrates destructuring for an individual property
  3. The third usage demonstrates destructuring for an individual sub-property, then first character under the title property

Finally, for the sake of example, all of the belopw would produce the same result:

const {
  title: { 0: { via String.prototype.toUpperCase.apply: dropCap } },
  title: { 0: { via firstChar => firstChar.toUpperCase(): dropCap } },
  title: { via ([firstChar]) => firstChar.toUpperCase(): dropCap },
  title: { via string => string[0].toUpperCase(): dropCap },
  via ({ title }) => title.toUpperCase(): dropCap,
  via article => article.title.toUpperCase(): dropCap,
  via article => String.prototype.toUpperCase.apply(article.title): dropCap,
}

Thanks again! I really feel like we're onto something here. I'd love to hear your thoughts on all this as well.

I do agree with Bergus on the general idea of using something within the object destructuring to refer to the whole object.

It is jarring to have, in the same object-destructuring statement, some properties that extract a single property and others that operate on the whole object and derive a value. Even if we switch the order of where "via" was used, I think it's still a bit odd. If we really want something like this, perhaps, we can find a separate syntax that lets you use multiple lvalues for the same rvalue. The proposed pattern-matching proposal has been making some good headway, perhaps we can borrow the & combinator from that proposal and allow it's used during normal assignment as well, like this:

const x & y = 2
console.log(x) // 2
console.log(y) // 2

This, in combination with via, would allow you to do the following (I'll put via back to the right side for these examples):

const {
  title,
  subtitle,
  content,
  category,
}
  & slug via buildSlug
  & date via getDaysSince
  & wordCount via getWordCount
  & charCount via getCharCount
  = article;

If we don't like having via on the right-hand side, I'm ok moving it to the left-hand side, but still having it mean the same thing. Though, I would do the syntax slightly different as follows:

const { via toString oldPropName: newPropName } = obj;
const { via toString propName } = obj;
const [via entryName entry] = array;
1 Like

I agree, I didn't expect this idea could satisfy TC39.

No, that won't work, destructuring is designed to have the property name on the left and the target expression (which might contain nested destructuring patterns) on the right.

Actually I think it would work, as far as grammar unambiguity is concerned, to put the "call" on the left side around the property name:

const {
  getDaysSince(date): days,
  getWordCount(content): wordCount,
  getCharCount(["content"]): charCount,
  buildSlugURL(title): {path: slugPath, searchParams: slugParams},
} = article;

But that's still confusing, I'll expect people trying to pass multiple arguments and also things that are not properties into these "calls" which should not work.
I didn't remember @theScottyJam's coercion discussion, but I think I would prefer to have the function in between the property name and the target:

const {
  date via getDaysSince: days,
  content via getWordCount: wordCount,
  ["content"] via getCharCount: charCount,
  title via buildSlugURL: {path: slugPath, searchParams: slugParams},
} = article;

And if you wanted to call the function in a different way, a function expression would work:

  title via (t => buildSlugURL(location.href, t, {relative: true})): {path: slugPath, searchParams: slugParams},

Then don't use destructuring for these.
Although indeed using the getters syntax from the extensions proposal would enable this.

Though, how would that work for arrays desturcturing? I think if we were to introduce a "via" syntax, we'd want it to work with object destructuring, array destructuring, and no destructuring at all.

Examples with the currently proposed syntax. Much of this wouldn't be possible if we moved the "via" keyword elsewhere, depending on how we move it.

const [
  x via asString,
  [valueFromSubArray] via coerceToArray,
] = ...

const {
  x via asString,
  y: Y via asString,
  z: { valueFromSubObj } via coerceToObj
} = ...

function doThing(x via asString, y via assertThisIsAString) { ... }

const [x via asString via assertThisIsANumber] = ...

try {
  ...
} catch (err via coerceToError) {
  ...
}

I like how the current syntax can just "click" anywhere where an lvalue is expected.

@theScottyJam @bergus First, I just wanted to say I appreciate all the careful consideration you've given this proposal, especially on the weekend. Thank you!

Instead of rushing to respond with my thoughts, I took some time to think about everything you both suggested, and I think it's right on the money. Having given some thought to the placement of the via, I agree with @theScottyJam's point (I think you both pointed it out, though) that via should be in the value and not the key, especially so it works consistently in all contexts— Object destructuring, Array destructuring, and outside of destructuring (like in your try…catch example).

To ensure I understand the proposal(s) here, would the below assertion be correct?

const apiResponse = [
  {
    title: 'A single day in Paris',
    subtitle: 'Let\'s talk Eiffel…',
    content: 'Lorem ipsum yada yada',
    category: 'travel',
    date: '7/8/2022 19:05:46 GMT-0400',
    likes: 22,
    comments: 5,
    views: 364,
  },
];

const makeUpperCase = (string) => string.toUpperCase();
const buildSlug = (string) => string.toLowerCase().replaceAll(' ', '-');
const getCharCount = (string) => string.length;
const getWordCount = (string) => string.split(' ').length;
const getDaysSince = (date) => (new Date() - new Date(date)) / 864e5;
const countEngagements = ({ likes, comments, views }) => {
  return likes + comments + views;
};

const {
  length: articlesCount,
} & [
  firstArticle,
  ...nonFirstArticles
] & [
  {
    title: firstArticleUpperCaseTitle via makeUpperCase,
    // spelling out the uppercase function on this one for the sake of example
    title: [dropCap] via (str => str.toUpperCase()),
    // drop cap example again just to ensure this would work identically using Object vs. Array destructuring
    title: { 0: anotherDropCap } via (str => str.toUpperCase()),
    title: firstArticleSlug via buildSlug,
    title: firstArticleTitleCharCount via getCharCount,
    subtitle: firstArticleSubtitle,
    content: firstArticleCharCount via getCharCount,
    content: firstArticleWordCount via getWordCount,
    date: daysSinceFirstArticle via getDaysSince,
  },
] & [
  firstArticleEngagements via countEngagements,
] & {
  // another example of the same `countEngagements` example above just to ensure these would both work identically; I'm pretty sure they would
  0: alsoFirstArticleEngagements via countEngagements,
  // third time's a charm, right?
  0: alsoFirstArticleEngagementsAgain via countEngagements,
} & articles /* aliasing `apiResponse` as `articles` */ = apiResponse;

This example is likely not a great example of how someone should use this technique, but would this work as expected? Am I missing anything?


So there are a few pieces to this—

  • extending the existing pattern matching (&) proposal to work with the (via) proposal, or vice-versa depending on which one is farther ahead or gets approved sooner
  • accounting for destructuring assignments in whatever spec is put together for via so it works with both Object and Array destructuring as well as outside of destructuring contexts
  • BONUS (mentioned earlier in this discussion): new Array method which works in a near-inverted nature to the existing Array.prototype.map method, processing a single value/object through an array of functions rather than an array of values/objects through a single function.

Would any of these three require a separate proposal? I'm guessing the third (BONUS) might.

Concerning the first two, would any updates need to be made to the related proposals currently in review to account for these new use cases and requirements? If so, what are the best next steps forward for those?

Is there anything left outside of those that this proposal I opened would need to accomplish, or should this proposal remain open purely as a proposal to extend both those existing proposals until TC39 makes a more formalized decision?

Thanks again!

Yep, all of that looks correct to me.

If this all were to actually go through, I'm sure it would be three separate proposals. The first one is the "via" proposal, the "&" outside of pattern-matching proposal (which would be a follow-on proposal that could happen after patter-matching goes through), and the bonus array method proposal. None of the current proposals would need to be updated to make this happen.

Here, by "proposal" I assume you're talking about this thread.

Well, I gave the "&"-combinator idea as the best idea I could think of to fulfill the use-case you had. Though, I myself am not actually convinced of its utility yet - there's a high bar for adding new syntax to the language and I'm not sure this passes. It helps that we're able to piggy-back off of existing syntax (from pattern-matching), making it so we're not spending as much of a syntax budget to introduce it.

So, perhaps it's worth starting a discussion about why the "&"-combinator-anywhere idea is a good one, and all of the value it can bring into the language - and this can be done either in this thread or a new one, up to you. For the "&"-combinator-anywhere idea to get through, it'll need to be good enough to be able to stand on its own, even if the "via" idea never makes it.

I appreciate the thorough review and response to my questions. I agree. This thread has already proven to be very productive, so let's continue the discussion here; as far as I can see, there is no need to start a new one at this point.

Process-wise, who creates and submits the official proposal(s) to TC39? If/once we reach a level of completion on the spec for a prospective new feature, would you or someone else usually "champion" the spec and take it forward as an actual proposal, or would I create the technical documentation at that stage and submit it myself? I certainly wouldn't mind doing so, though I'm not much of a technical writer myself, so I'll work to absorb all the know-how I can here, if that's the usual process, though I'm all for the champion-ing approach.


Okay— I'll back up from the process bit to avoid getting ahead of myself here. Let's discuss the "&-combinator-anywhere" idea, though this is more specific to the destructuring use case unless there is another use case one of us mentioned earlier in this thread that I'm failing to account for. Please let me know if that's the case.

Here are some reasons why I think this could be an invaluable tool to add to the toolbox that is the ECMAScript standard:

  • First—as demonstrated previously in this thread— introducing & in this capacity opens the door to new possibilities, namely in destructuring. Without this, separate assignments would need to be used to destructure from the same object.

    • WITHOUT using & in destructuring assignment
      const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];
      
      const [firstObj, ...otherObjs] = arr;
      const [{ id: firstId }] = arr;
      const { length: numberOfIds } = arr;
      
    • WITH using & in destructuring assignment
      const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];
      
      const [firstObj, ...otherObjs] & [{ id: firstId }] & { length: numberOfIds } = arr;
      
      I'm sure many devs (and Prettier) would break this up into separate lines as well when many of these are stacked into one assignment, so it doesn't get too visually bloated, which comes together nicely:
      const [firstObj, ...otherObjs]
            & [{ id: firstId }]
            & { length: numberOfIds } = arr;
      
  • alignment with software engineering principles

    • D.R.Y.: One of the most significant benefits of destructuring assignment methodology is that it allows a developer to quickly retrieve multiple values from a single object without pulling each piece out manually with a separate assignment.
  • choosing an operator (&): Why choose & and not something else? Several reasons…

    • & — other than just representing the word "and" in the English language — is not only familiar, but this newly proposed usage is also familiar to its existing and proposed uses:

      • Existing functionality: in data comparison and math, it is the bitwise-and operator
      • Existing proposal: in the pattern matching proposal, & is also used as "and" to conjoin values
      • Prospective proposal (this thread): the generally understood idea of "and" (whether bitwise or logical) behind the symbol & could also be used to conjoin/delineate values
    • Comparatively, it is the logical choice. Other operators…

      • Pipe (|): In spreadsheets, this is sometimes done with the pipe symbol (|), but that would be counterintuitive for this proposal, as the pipe generally means bitwise/logical-OR.
      • Semicolon (;): The semicolon could not be used as that is JavaScript's termination operator.
      • Comma (,): The comma would not work either since that plays another essential role in assignments— creating separate assignments within a single "assignment block" (not having to write const/let/var each time).

      Ampersand (&): The ampersand seems the logical choice, further extending an operator already widely used by the community that feels familiar and would welcome new usage in a familiar domain without introducing a brand new symbol as an operator.


I'm happy to argue the case for & plenty more, and I'm sure I'll be doing that as long as this idea lives and if/until it reaches a stable release of ECMAScript. What other information or context can I provide to strengthen my case? More examples?

For the other two new prospective proposals, would a similar justification like this one :point_up:t3: be helpful? Can/should I draft those up and add them to this thread?

Not me :) - I'm just another community member, like you, who enjoys engaging in discussions around people's ideas. The people who are actually from the committee, and who are able to champion a proposal have "delegate" next to their name - I haven't seen any of them participate in this thread yet, though that doesn't mean they haven't read it.

Here's the guide on how to create a proposal. Specifically, you'd create a Github repository patterned after this one and fill in the README according to its instructions. Then, you'd need to convince a champion that the proposal is worth pushing through. You can choose to make the proposal repositories now, or wait until you get some feedback from delegates to see how they feel about the idea and if they have suggestions for it, I've seen people do it either way. Certainly, a lot of the arguments you put here could be put in a proposal repo.


The "ap" helper function we discussed earlier would also solve your use case, but without the need for additional syntax, correct? Knowing how difficult it is for TC39 to accept new syntax, you'll probably find a much easier time asking for a new method instead of new syntax.

I know there has recently been a push to get some functional-related functions into the language, and an ap function would fit right into that sort of thing.

I think this is similar to an idea by @rbuckton - the syntax is along the lines of:

const x = { prop: 1 };
const { Modifier(prop) } = x;

Modifier would conform to some protocol, e.g a symbol method, that would receive x.prop as an input. prop is then bound to the result.

1 Like

Thanks @aclaymore for the heads up! Could you share a link to the proposal for that? I'd love to read up on it more.

In terms of syntax, I think that might fall into the same issue my former spec did, where I wanted to use via on the left side (keys) rather than the right side (values). It could still work, but it's a bit messier for Array destructuring or outside of destructuring contexts.

Maybe not the most readable but adding some “helpers” gets quite close to the initial post:

// helpers
function* repeat(val) {
  for (;;) yield val;
}
function _() {}
// usage:
const repeat([
  pieceKey(key),
  elmAlg(alg),
  elmPos(pos),
  _(cid = `${key}${alg}`),
]) = document.querySelector('.chess-piece');

console.log(key, alg, pos, cid);

I don’t think there is a formal proposal, it was in a gist. I’ll see if I can find it again and share it

1 Like

Thanks!

Is the idea that the variable would be named the same as the unmodified variable unless aliased? So the Object version would need to be aliased (e.g. { Modifier(originalProp): aliasVariable }), and the Array version would use the new name within the modifier itself (e.g. [Modifier(aliasVariable)])?

@aclaymore Just to follow up, I certainly see the resemblance of my proposed feature here using the separately proposed via and @rbuckton's idea. They work to achieve the same goal.

Personally, after deliberating with @bergus and @theScottyJam, I believe via to be a simpler and more comprehensible API, especially as it can be used anywhere with the same syntax. My originally suggested syntax was closer to Ron's but a few holes were validly punched in that idea, and I quite like the syntax we arrived at as a result.


@theScottyJam Yes, I agree that the ap Array method would solve my use-case here and may get pushed along more quickly. Would the next logical step here be to provide a justification for that as well before creating an official proposal?


Thanks all :pray:t3: