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

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

Dmitry.
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to