Deep JavaScript

## 3 The destructuring algorithm

In this chapter, we look at destructuring from a different angle: as a recursive pattern matching algorithm.

The algorithm will give us a better understanding of default values. That will be useful at the end, where we’ll try to figure out how the following two functions differ:

``````function move({x=0, y=0} = {})         { ··· }
function move({x, y} = { x: 0, y: 0 }) { ··· }``````

### 3.1 Preparing for the pattern matching algorithm

A destructuring assignment looks like this:

``«pattern» = «value»``

We want to use `pattern` to extract data from `value`.

We will now look at an algorithm for performing this kind of assignment. This algorithm is known in functional programming as pattern matching (short: matching). It specifies the operator `←` (“match against”) that matches a `pattern` against a `value` and assigns to variables while doing so:

``«pattern» ← «value»``

We will only explore destructuring assignment, but destructuring variable declarations and destructuring parameter definitions work similarly. We won’t go into advanced features, either: Computed property keys, property value shorthands, and object properties and array elements as assignment targets, are beyond the scope of this chapter.

The specification for the match operator consists of declarative rules that descend into the structures of both operands. The declarative notation may take some getting used to, but it makes the specification more concise.

#### 3.1.1 Using declarative rules for specifying the matching algorithm

The declarative rules used in this chapter operate on input and produce the result of the algorithm via side effects. This is one such rule (which we’ll see again later):

• (2c) `{key: «pattern», «properties»} ← obj`

``````«pattern» ← obj.key
{«properties»} ← obj``````

This rule has the following parts:

• (2c) is the number of the rule. The number is used to refer to the rule.
• The head (first line) describes what the input must look like so that this rule can be applied.
• The body (remaining lines) describes what happens if the rule is applied.

In rule (2c), the head means that this rule can be applied if there is an object pattern with at least one property (whose key is `key`) and zero or more remaining properties. The effect of this rule is that execution continues with the property value pattern being matched against `obj.key` and the remaining properties being matched against `obj`.

Let’s consider one more rule from this chapter:

• (2e) `{} ← obj` (no properties left)

``// We are finished``

In rule (2e), the head means that this rule is executed if the empty object pattern `{}` is matched against a value `obj`. The body means that, in this case, we are done.

Together, rule (2c) and rule (2e) form a declarative loop that iterates over the properties of the pattern on the left-hand side of the arrow.

#### 3.1.2 Evaluating expressions based on the declarative rules

The complete algorithm is specified via a sequence of declarative rules. Let’s assume we want to evaluate the following matching expression:

``{first: f, last: l} ← obj``

To apply a sequence of rules, we go over them from top to bottom and execute the first applicable rule. If there is a matching expression in the body of that rule, the rules are applied again. And so on.

Sometimes the head includes a condition that also determines if a rule is applicable – for example:

• (3a) `[«elements»] ← non_iterable`
`if (!isIterable(non_iterable))`

``throw new TypeError();``

### 3.2 The pattern matching algorithm

#### 3.2.1 Patterns

A pattern is either:

• A variable: `x`
• An object pattern: `{«properties»}`
• An Array pattern: `[«elements»]`

The next three sections specify rules for handling these three cases in matching expressions.

#### 3.2.2 Rules for variable

1. `x ← value` (including `undefined` and `null`)
``x = value``

#### 3.2.3 Rules for object patterns

• (2a) `{«properties»} ← undefined` (illegal value)

``throw new TypeError();``
• (2b) `{«properties»} ← null` (illegal value)

``throw new TypeError();``
• (2c) `{key: «pattern», «properties»} ← obj`

``````«pattern» ← obj.key
{«properties»} ← obj``````
• (2d) `{key: «pattern» = default_value, «properties»} ← obj`

``````const tmp = obj.key;
if (tmp !== undefined) {
«pattern» ← tmp
} else {
«pattern» ← default_value
}
{«properties»} ← obj``````
• (2e) `{} ← obj` (no properties left)

``// We are finished``

Rules 2a and 2b deal with illegal values. Rules 2c–2e loop over the properties of the pattern. In rule 2d, we can see that a default value provides an alternative to match against if there is no matching property in `obj`.

#### 3.2.4 Rules for Array patterns

Array pattern and iterable. The algorithm for Array destructuring starts with an Array pattern and an iterable:

• (3a) `[«elements»] ← non_iterable` (illegal value)
`if (!isIterable(non_iterable))`

``throw new TypeError();``
• (3b) `[«elements»] ← iterable`
`if (isIterable(iterable))`

``````const iterator = iterable[Symbol.iterator]();
«elements» ← iterator``````

Helper function:

``````function isIterable(value) {
return (value !== null
&& typeof value === 'object'
&& typeof value[Symbol.iterator] === 'function');
}``````

Array elements and iterator. The algorithm continues with:

• The elements of the pattern (left-hand side of the arrow)
• The iterator that was obtained from the iterable (right-hand side of the arrow)

These are the rules:

• (3c) `«pattern», «elements» ← iterator`

``````«pattern» ← getNext(iterator) // undefined after last item
«elements» ← iterator``````
• (3d) `«pattern» = default_value, «elements» ← iterator`

``````const tmp = getNext(iterator);  // undefined after last item
if (tmp !== undefined) {
«pattern» ← tmp
} else {
«pattern» ← default_value
}
«elements» ← iterator``````
• (3e) `, «elements» ← iterator` (hole, elision)

``````getNext(iterator); // skip
«elements» ← iterator``````
• (3f) `...«pattern» ← iterator` (always last part!)

``````const tmp = [];
for (const elem of iterator) {
tmp.push(elem);
}
«pattern» ← tmp``````
• (3g) `← iterator` (no elements left)

``// We are finished``

Helper function:

``````function getNext(iterator) {
const {done,value} = iterator.next();
return (done ? undefined : value);
}``````

An iterator being finished is similar to missing properties in objects.

### 3.3 Empty object patterns and Array patterns

Interesting consequence of the algorithm’s rules: We can destructure with empty object patterns and empty Array patterns.

Given an empty object pattern `{}`: If the value to be destructured is neither `undefined` nor `null`, then nothing happens. Otherwise, a `TypeError` is thrown.

``````const {} = 123; // OK, neither undefined nor null
assert.throws(
() => {
const {} = null;
},
/^TypeError: Cannot destructure 'null' as it is null.\$/)``````

Given an empty Array pattern `[]`: If the value to be destructured is iterable, then nothing happens. Otherwise, a `TypeError` is thrown.

``````const [] = 'abc'; // OK, iterable
assert.throws(
() => {
const [] = 123; // not iterable
},
/^TypeError: 123 is not iterable\$/)``````

In other words: Empty destructuring patterns force values to have certain characteristics, but have no other effects.

### 3.4 Applying the algorithm

In JavaScript, named parameters are simulated via objects: The caller uses an object literal and the callee uses destructuring. This simulation is explained in detail in “JavaScript for impatient programmers”. The following code shows an example: function `move1()` has two named parameters, `x` and `y`:

``````function move1({x=0, y=0} = {}) { // (A)
return [x, y];
}
assert.deepEqual(
move1({x: 3, y: 8}), [3, 8]);
assert.deepEqual(
move1({x: 3}), [3, 0]);
assert.deepEqual(
move1({}), [0, 0]);
assert.deepEqual(
move1(), [0, 0]);``````

There are three default values in line A:

• The first two default values allow us to omit `x` and `y`.
• The third default value allows us to call `move1()` without parameters (as in the last line).

But why would we define the parameters as in the previous code snippet? Why not as follows?

``````function move2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}``````

To see why `move1()` is correct, we are going to use both functions in two examples. Before we do that, let’s see how the passing of parameters can be explained via matching.

#### 3.4.1 Background: passing parameters via matching

For function calls, formal parameters (inside function definitions) are matched against actual parameters (inside function calls). As an example, take the following function definition and the following function call.

``````function func(a=0, b=0) { ··· }
func(1, 2);``````

The parameters `a` and `b` are set up similarly to the following destructuring.

``[a=0, b=0] ← [1, 2]``

#### 3.4.2 Using `move2()`

Let’s examine how destructuring works for `move2()`.

Example 1. The function call `move2()` leads to this destructuring:

``[{x, y} = { x: 0, y: 0 }] ← []``

The single Array element on the left-hand side does not have a match on the right-hand side, which is why `{x,y}` is matched against the default value and not against data from the right-hand side (rules 3b, 3d):

``{x, y} ← { x: 0, y: 0 }``

The left-hand side contains property value shorthands. It is an abbreviation for:

``{x: x, y: y} ← { x: 0, y: 0 }``

This destructuring leads to the following two assignments (rules 2c, 1):

``````x = 0;
y = 0;``````

This is what we wanted. However, in the next example, we are not as lucky.

Example 2. Let’s examine the function call `move2({z: 3})` which leads to the following destructuring:

``[{x, y} = { x: 0, y: 0 }] ← [{z: 3}]``

There is an Array element at index 0 on the right-hand side. Therefore, the default value is ignored and the next step is (rule 3d):

``{x, y} ← { z: 3 }``

That leads to both `x` and `y` being set to `undefined`, which is not what we want. The problem is that `{x,y}` is not matched against the default value, anymore, but against `{z:3}`.

#### 3.4.3 Using `move1()`

Let’s try `move1()`.

Example 1: `move1()`

``[{x=0, y=0} = {}] ← []``

We don’t have an Array element at index 0 on the right-hand side and use the default value (rule 3d):

``{x=0, y=0} ← {}``

The left-hand side contains property value shorthands, which means that this destructuring is equivalent to:

``{x: x=0, y: y=0} ← {}``

Neither property `x` nor property `y` have a match on the right-hand side. Therefore, the default values are used and the following destructurings are performed next (rule 2d):

``````x ← 0
y ← 0``````

That leads to the following assignments (rule 1):

``````x = 0
y = 0``````

Here, we get what we want. Let’s see if our luck holds with the next example.

Example 2: `move1({z: 3})`

``[{x=0, y=0} = {}] ← [{z: 3}]``

The first element of the Array pattern has a match on the right-hand side and that match is used to continue destructuring (rule 3d):

``{x=0, y=0} ← {z: 3}``

Like in example 1, there are no properties `x` and `y` on the right-hand side and the default values are used:

``````x = 0
y = 0``````

It works as desired! This time, the pattern with `x` and `y` being matched against `{z:3}` is not a problem, because they have their own local default values.

#### 3.4.4 Conclusion: Default values are a feature of pattern parts

The examples demonstrate that default values are a feature of pattern parts (object properties or Array elements). If a part has no match or is matched against `undefined` then the default value is used. That is, the pattern is matched against the default value, instead.