Day 2 meeting notes

Brendan Eich brendan at mozilla.com
Fri Jul 30 14:37:34 PDT 2010


On Jul 30, 2010, at 1:38 PM, Dmitry A. Soshnikov wrote:

> Another thing to mention regarding array comprehensions is /pattern matching/ (in less common, but related to JS, case -- /destructing assignment/). Currently, it's implemented in JS 1.7 in simple for/each-in loops:
> 
> for each ({a: x} in [{a: 10}, {a: 20}, {a: 30}]) {
>  alert(x); // 10, 20, 30
> }
> 
> Yes, it can be useful to pattern match {a: x} extracting a value of the "a" property into the "x" variable. Array comprehensions of JS 1.7 in contrast with a loop do not have such sugar:
> 
> let a = [x for each ({a: x} in [{a: 10}, {a: 20}, {a: 30}]) if (x > 2)]; // SyntaxError

You don't need each to make that work in JS1.7:

js> let a = [x.a for each (x in [{a: 10}, {a: 20}, {a: 30}]) if (x.a > 2)];
js> a
[10, 20, 30]

Notice that you can use each in JS1.7 after for (E4X was in JS1.6 and up).

But you are quite right that we did not allow the same left-hand sides of 'in' in comprehensions as we did in equivalent loops:

js> let a = []; for each ({a: x} in [{a: 10}, {a: 20}, {a: 30}]) if (x > 2) a[a.length] = x; a
[10, 20, 30]

That is just a flaw in JS1.7, possibly even not a design flaw but an implementation bug (I honestly don't remember).

For Harmony, we do not propose to standardize |for each|. Instead, the iteration and array comprehensions proposals for Harmony (see the wiki) propose that programmers choose keys, values, items (properties), or other iteration protocols by saying what they mean more precisely on the right-hand side of 'in':

for (k in keys(o)) ...
for (v in values(o)) ...
for ([k, v] in properties(o)) ... // Python's "items"

This seems better in TC39 members' views than adding ambiguous 'each' as a contextual keyword.


> Also using pattern matching, it's useful sometimes to filter needed values of an array in the pattern matching parameter itself, but not using filter section (actually, it's hard to use filter section to filter exactly values which are not of the needed structure). For example (code on Erlang with its list comprehensions):
> 
> List = [{1,2}, skip, {3,4}],
> 
> FilteredList = [X + Y || {X, Y} <- List].
> 
> Result is: [3, 7]. Pattern matching filtered atom `skip' and took only {X, Y} structures (1st and 3rd elements in list) extracting values into the X and Y variables.
> 
> This is useful feature, I was needed it on practice. In contrast lists:map + lists:filter (that's desugared list comprehensions) cannot handle this case, because for the `skip' atom will be `bad match' error and we can't map a list the same elegant as with list comprehension:
> 
> List = [{1,2}, skip, {3,4}],
> lists:map(fun({X, Y}) -> X + Y end, List).  % bad_match error for the second element - `skip' atom
> 
> Syntactically JavaScript has similar construction, but semantically result differs:
> 
> for each ({a: x, b: y} in [{a: 10, b: 20}, "skip", {a: 30, b: 40}]) {
>  alert(x, y); // 10, 20 | undefined, undefined | 30, 40
> }

Syntax aside, destructuring in JS is not irrefutable match. It is simply shorthand for assigning (and declaring) variables whose names are supplied in the "value" position in array and object initialisers, where the assigned values for these variables come from property values named by the keys in the corresponding "key" position. Failure in the sense of pulling undefined out of a non-existing property *is* an option:

js> let [x, y] = {not_an_array: 42};
js> x
js> y

and of course if you dig deeper, you can get failure dereferencing undefined:

js> let [x, [y, z]] = {nor_here: 99};
typein:20: TypeError: (void 0) is undefined

(SpiderMonkey uses (void 0) not undefined since prior to ES5, the global property named undefined was writable and could be spoofed.)


> String "skip" isn't pattern matched, but the object is created with `undefined' values for x and y. And again for array comprehensions this shows SyntaxError (that, for consistency with for/each-in loop should not).

I agree that some fairly common JS use-cases want irrefutable match. Dave Herman pointed out how the SpiderMonkey and Rhino extended catch syntax, guarded catches, is a kind of matching:

try {
   throw random_exception_generator();
} catch (e if typeof e == "number") {
   ...
} catch (e if typeof e == "string") {
   ...
} catch {
  // default case, if you forget it e will be rethrown for you
}

This was proposed for ES3 but not accepted. It has the advantage cited by the comment in the default catch clause.

Perhaps there's a generalization of such guards, which could re-use the initialiser-derived pattern syntax from destructuring, and which would provide irrefutable match as a primitive with good compositionality.

/be


More information about the es-discuss mailing list