2
$\begingroup$

Some languages allow you to write something like

x, y, z = (1, 2, 3)

Which is equivalent to

x = 1
y = 2
z = 3

What are some of the syntax constructs that exist that perform variable destructuring/unpacking?

$\endgroup$
1
  • 1
    $\begingroup$ Generally the syntax for destructuring will mirror the syntax for constructing the same data. So if your language has data structures which can be destructured, then you presumably already have syntax for them. $\endgroup$
    – kaya3
    Commented May 18, 2023 at 12:48

2 Answers 2

5
$\begingroup$

For JavaScript, there are two valid syntaxes, depending on what you are destructuring. In both cases they attempt to mirror the data structure they work on:

Array destructuring

Uses [ ] to enclose the variables that need to be unpacked and does so positionally - the first variable takes on the first value, the second variable takes on the second value, etc.:

const someArray = [1, 2, 3];
const [x, y, z] = someArray;

Positions can be skipped via elision - by not supplying a variable:

const someArray = [1, 2, 3];
const [x, , z] = someArray;

Which is essentially the same as:

const x = someArray[0];
const z = someArray[2];

Rest syntax ...

The remainder of the unpacked values can be collected into a single array by using the syntax ...<variable name>, for example

const someArray = [1, 2, 3];
const [x, ...others] = someArray;

will have others = [2, 3]

Not just for arrays

The common name is "array destructuring" but it does not only work on arrays but any iterable. For example sets or generators:

const someSet = new Set().add(1).add(2).add(3);
const [x, y, z] = someSet;
const someGenerator = function* () {
    yield 1;
    yield 2;
    yield 3;
}
const [x, y, z] = someGenerator();

In a lot of common cases it is more or less the same whether you use it on arrays or not. However, that is not necessarily correct for all iterables - if consuming an iterable keeps producing values, the array destructuring will keep drawing, rather than only get the same position:

const someArray = [1, 2, 3];
const [x] = someArray;
const [y] = someArray;
const [z] = someArray;

Will produce x = 1, y = 1, and z = 1 because each of them is assigned the first position of the array. Destructuring the array produces a new iterator each time that starts from the start.

While

const someGenerator = function* () {
    yield 1;
    yield 2;
    yield 3;
}

const [x] = someGenerator;
const [y] = someGenerator;
const [z] = someGenerator;

Will produce x = 1, y = 2, and z = 3 because there is one iterator in total that keeps producing values.

Object destructuring

Uses { } to enclose the variables that need to be unpacked and does so by name:

const someObject = {x: 1, y: 2, z: 3 };

const { x, y, z } = someObject;

Essentially the same as:

const someObject = {x: 1, y: 2, z: 3 };

const x = someObject.x;
const y = someObject.y;
const z = someObject.z;

The longer form also allows for re-mapping the properties to differently named variables:

const someObject = {x: 1, y: 2, z: 3 };

const { a: x, b: y, c: z } = someObject;

Essentially the same as:

const someObject = {x: 1, y: 2, z: 3 };

const a = someObject.x;
const b = someObject.y;
const c = someObject.z;

Rest syntax ...

Very similar to how the array destructuring syntax can use ...<variable name> to collect the rest of the unpacked values, the same can be done with object destructuring to collect all key/values that have not been extracted otherwise:

const someObject = {x: 1, y: 2, z: 3 };

const { x, ...others } = someObject;

will have others = { y: 2, z: 3 }

Combining these

These can be arbitrarily nested and combined to access any property and value:

const users = [
    { 
        name: "Fred", 
        userInfo: { likes: "bowling" }, 
        addresses: ["Town of Bedrock", "some previous address"] 
    }
];

const [
    { 
        name, 
        userInfo: { likes: hobby }, 
        addresses: [lastAddress] 
    }
] = user;

Will extract: name = "Fred", hobby = "bowling", and lastAddress = "Town of Bedrock".

$\endgroup$
1
  • $\begingroup$ You can also destructure within function parameters ({ name }) => name, or use iterable destructuring with strings. $\endgroup$
    – Jacob
    Commented May 18, 2023 at 13:48
1
$\begingroup$

An approach I'd like to see would be a construct that combines new automatic-scope object declarations and initializer-like constructs with a compound statement. Perhaps something like:

union ( int x=foo->bar : 4,8, y = baz->bongo : 3,9;)
{
  ...
}

The syntax would be identifiable by the opening parenthesis following the union keyword--something that would otherwise be impossible.

The "initializers" would need to be lvalues, optionally followed by a bit width and offset. Such a syntax would invite a compiler to behave as though the declared identifiers were objects of the indicated type which were loaded any time before their value was used, and written back any time after their value was modified, in any manner consistent with normal "caching" semantics (if a compiler modifies the temporary object, future reads would need to come from that object rather than the original until the change was written back; if the compiler reads and caches the original object, but then modifies the orginal object, it would need to ensure that the cached copy was updated or reloaded before it was used againm etc.).

If two or more initializers are bound to the same side-effect-free lvalue expression, a compiler would have to allow for the possibility that changes to one might affect the other, but such allowance would not be required if lvalue expressions have side effects or don't match each other.

Note that while this construct would be semantically most useful when handlings things like bit fields, it could also allow compilers the freedom to keep an object that would be updated multiple times in a register, and consolidate multiple writes to an object, when doing so would be useful, but allow a compiler to flush such cached objectss in cases where the alternative would be an extra register spill (thus allowing what would be the sequence:

load x into r0
... do some stuff with r0
push r0
... do some stuff that needs to use r0 for some other purpose
... pop r0
store r0 into x

to be improved by storing r0 into x before the stuff that would use r0 for some other purpose, and avoiding the need to pop it and then store it.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .