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

Reply via email to