Day 2 meeting notes

Dmitry A. Soshnikov dmitry.soshnikov at
Sat Jul 31 04:45:28 PDT 2010

On 31.07.2010 1:37, Brendan Eich wrote:
> 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).

Yeah, I just mention exactly an element's structure filtering, not 
touching _real_ filtering within already /structure filtered/ elements. 
 From the other hand, maybe current JS destructuring (but not 
irrefutable match) even better -- in respect that it's more flexible -- 
structure filtering can be moved to the if-filter section (i.e. 
technically it's easy to achieve, I just meant some sugar, but at the 
same time I thought, this sugar (irrefutable match) is not so flexible 
for JS):

// only first element will be in "a" array

let a = [x
   for each (x in [{a: 10, b: 20}, {a: 1, b: 20}, "skip", {a: 30}])
     if (
       // filtering needed structure first
       (typeof x == "object") && ("a" in x) && ("b" in x) &&
       // and then already real filters within needed structure elements
       x.a > 1

It's more flexible, because lets (without exact structure filtering) get 
{a: x} from {a: 10, b: 20} and {a: 30}.

> 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.

So, they (keys, values, properties) result iterators. Are the keys(o) is 
the same, but just more explicit version of a simple iteration over the 
`o' object?

for (k in o)  and for (k in keys(o))  -- are they the same (excluding 
implementation, e.g. lazy evaluation possibly or sort of, only the end 
result is interesting)?

If `keys`, `values` and `properties` simple functions are not global 
functions, does a user should always import them from basic iteration 
module (i.e. "if you want this sugar -- then import, if not -- use old 
for-in over properties")? Or will they be imported automatically before 
evaluation every module/context? If the later, then they are 
(semantically) global functions.

Actually, for programmers familiar with iterators, the following case 
shouldn't cause ambiguity:

for (k in o) // default -- always over keys
for (k in keys(o)) // the same, explicitly mention keys

let value = values(o);
for (v in values) // over values, because a programmer see that he 
iterates over the special iterator object

etc. It seems convenient.

For not so familiar -- yeah, it may cause some ambiguities when we can't 
say exactly looking on just one line -- for (z in o) -- over what does 
it iterate (in case if `o' was set something else of course) -- keys, 
values, "items"? So the programmer should back and look what is `o' -- 
in contrast with `for each' or `for (values of object)', etc. additional 
syntax keywords.

>> 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

Yeah, I understand how it works (and mentioned that it seems even more 
flexible than Erlang's irrefutable match; but, actually, Erlang, being 
completely functional, has pattern matching in the core ideology. JS is 
different, so possibly, repeat, current JS 1.7 models for this more 

> 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.)

Yeah, I got it. However, I see undefined in 1.7 shell (possibly it's 
later build, maybe even 1.8 don't remember):

js> version(170)
js> let [x, [y, z]] = {nor_here: 99};
typein:2: TypeError: undefined has no properties

>> 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.

Yeah, I saw (and used) these guards in Rhino, useful. However, may be 
replaced with if-statement in one catch.

> 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

Maybe; if will be such cases (and very needed sound cases), it's 
possible to provide such constructs.


More information about the es-discuss mailing list