TL;DR: We propose to add functionalities to the `for/of` loop that make all
features of generators (and iterators) available. Currently, the features
unavailable in `for/of` loops are: values sent to the generator that are
retrieved by the `yield` expression, and closing value produced by the
generator using the `return` statement. The pretext is to make it possible to
rewrite naturally `someArray.reduce(functionExpression)` et al. when used
inside a generator function, so that `functionExpression` could contain `yield`
statements, a problem raised in a recent thread.
In a recent thread [1], it has been noted that, when using `.forEach` and
friends inside a generator, you cannot use `yield` inside the callback.
There has been some reflections on how to fix generators in order to avoid that
defect; but it was taking the problem by the wrong side, for the issue does not
come from the generator function,
but from the `.forEach` construct: it uses a first-class callback function
where `for/of` uses a second-class block.
Let's see how to reimplement `.forEach` et al. with ES6 tools, so that it just
works. Consider a generic loop:
```
// `arr` is an array
for (let v of arr) {
// do something with with v
}
```
Suppose that you want to run the loop only for even-ranked items of the array.
You could do something like this:
```
Array.prototype.forEachEven = function(f, o) {
// For simplicity, we ignore the issue of sparse arrays.
for (let i = 0; i < this.length; i += 2) {
f.call(o, this[i], i, this)
}
}
arr.forEachEven(function(v) {
// do something with v
// But you can't use yield, break or return :-(
}, this)
```
However, instead of transforming the entire for/of loop, it is more on-focus to
act on the iteration part only. I mean the following:
```
function* gEven(iterable) {
let i = 0
for (let x of iterable) {
if (i % 2 === 0) {
yield x
}
i += 1
}
}
for (let v of gEven(arr)) {
// do something with with v
// You can use yield, break or return here, and it will just work :-)
}
```
As you see, no more callback, no more problem. (For sure, you can *also* use
`yield*` inside the loop in order to abstract away some code; but the focus of
this message is on the iteration protocol.)
Now (and from here the things are becoming interesting), let us try to
reimplement `Array.prototype.some` and `Array.prototype.reduce`
using that pattern:
```
r = arr.some(function(v) {
let condition
// compute `condition`
return condition
}, this)
r = arr.reduce(function(x, v) {
let combined
// combine `x` and `v` into `combined`
return combined
}, initialValue)
```
Here, we need:
(1) retrieve the intermediate results returned by the callback (`condition` and
`combined` in our examples);
(2) provide the final result of the loop (the value that'll be stored in `r`)
On the side of generators, all is already done, for you can:
(1) retrieve an intermediate result by evaluating a `yield` expression;
(2) produce the final result using a `return` statement.
or, more generally, in the case of iterators:
(1) an intermediate result is retrieved by the first argument of the `next`
method`;
(2) the final result is produced by returning `{ done: true, value: result }`.
Thus, we obtain:
```
function* gSome(iterator) {
for (let v of iterator) {
if (yield v)
return true
}
return false
}
function* gReduce(iterator, r) {
for (let v of iterator) {
r = yield [r, v]
}
return r
}
```
However, on the side of the `for/of loop`, there is currently (to my knowledge)
no way to send or retrieve results to/from the iterator being traversed.
Therefore, I propose to add the functionality:
(1) Sending an intermediate result to the iterator: use a `continue with
expression` statement.
(2) Producing the final result: The for/of loop becomes an expression, which
evaluates to either:
(a) the closing value sent by the iterator, or:
(b) the `expression` of a `break with expression` statement.
(There is no LineTerminator between `continue/break` and `with`. The use of the
reserved word `with` means that it can't be confused with a label.)
Using that syntax, our loops are written as following:
```
r = for (let v of gSome(arr)) {
let condition
// compute `condition`, using `yield` ad libitum
continue with condition
}
r = for (let [x, v] of gReduce(arr, initialValue)) {
let combined
// combine `x` and `v` into `combined`, using `yield` ad libitum
continue with combined
}
r = for (let x of itr) {
break with 42
}
// `r` will be equal to 42 if `itr` is not empty
```
Naturally, you can also use labels:
```
break label with expression;
continue label with expression;
```
It is true that the examples chosen are academic in the sense it isn't harder
to write:
```
var r = initialValue
for (let v of arr) {
let combined
// combine `r` and `v` into `combined`, using `yield` ad libitum
r = combined
}
```
Netherveless, it is a shame that some nice features of iteration protocol are
unavailable in `for/of` loops, which is probably intended to be the main
consumer of iterators.
(And, if nothing else, it is a nice opportunity for the `with` keyword to
redeem itself. :-P)
—Claude
[1] generators vs forEach:
https://mail.mozilla.org/pipermail/es-discuss/2013-July/031919.html
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss