And why not? Because yield is a statement

Yield is an expression.

Thanks for the correction. Yes, "yield expr" is an expression, syntactically.

It doesn't have the nice composition and code transformation properties that I usually associate with expressions, it imposes unusual restrictions
on its *context* and impedes functional abstraction:

1 though "yield" constructs expressions from expressions, it isn't a function (can't pass "yield" around or store it in a variable), nor is "yield expr" a function call. 2 1 can be worked around, but not with the usual tools of function definitions and calls - "yield" forces use of "function*" and "yield*" for abstracting over expressions containing it.

3 "yield" is disappointingly similar to "this", in being implicitly bound to
the next "function*" ("function", for "this"). Expressions referencing either "this" or "yield" cannot be wrapped in functions (btw, can generator bodies reference an outer "this"?), because this would cut the implicit binding. For "this", workarounds include arrow functions or "bind", for "yield", the only workaround is "yield*"+"function*" (or diving even deeper, with hand-written iterators). Having to use different function mechanisms for the latter is by design, so it is a workaround only from the perspective of wanting to use uniform tools for functional abstraction.

For instance, we cannot write

   function* g() {  (function(){ yield 1 })() }
   function* g() {  function y(x){ yield x } y(1) }

but have to write

   function* g() {  yield* (function*(){ yield 1 })() }
   function* g() {  function* y(x){ yield x } yield* y(1) }

and when I try to write a polyfill for for-of, I end up with two partial
fills (neither models early return):

   function forof(g,cb) {  // non-generator callbacks only
     var r;
     while(true) {
       r = g.next();
       if (r.done) break; // skip return value?
       cb(r.value);
     }
   }

   function* forofG(g,cb) {  // generator callbacks only
     var r;
     while(true) {
       r = g.next();
       if (r.done) break; // skip return value?
       yield* cb(r.value);
     }
   }

We could switch on the type of cb, and go down to handwritten iteration,
to unify the two partials into one, but then we'd still have to cope with different usage patterns at the "call" sites (call with "function" vs. "yield*" with "function*").

Why shouldn't I be able to traverse an array, using the ES5 standard
operations for doing so, yielding intermediate results from the
traversal (recall also that yield can return data sent in via .next,
for incorporation into such traversals)?

You certainly can, with one modification: using *ES6* standard
operations (external iterators vs the ES5 forEach internal iterator).
Generators and non-generator iterators and for-of and comprehensions
hang together really nicely in practice.

    function* g(){
        for(x of [1,2,3]) yield transform(x);
    }

You're suggesting to abandon ES5 array iteration patterns in favor
of more general ES6 iterator patterns. That would be okay (*), but

1 it leaves fairly new (ES5) API surface as legacy

2 generators do not compose as freely as iteration functions,
   because they are tied to special syntax and restricted contexts

(*) if we want to go down that route, then why join TypedArrays with Arrays, according to old-style iteration-API? Shouldn't both be covered by a common iterator-based API instead?

Hand-written iterators don't suffer from 2, but are somewhat awkward
to write in place, and expose their lower-level protocol. Perhaps the solution is a rich enough standard iterator library, with generators as local glue and iterator library functions for supporting more general functional abstraction and composition. Perhaps we need to play a bit more with such iterator library functions, to get a better feeling for the limitations imposed by generators, and to give my concerns a concrete form? I've put up a gist with a few obvious things I'd want to have (something like "zip" and "feed" should really be standard; the former often has syntax support in the form of "parallel" comprehensions, the latter is
needed if we want to use an input-dependent generator in a "for-of"):

   https://gist.github.com/clausreinke/6073990

and there are several things I don't like, even at this simple stage:

- if you compare the versions that use "for-of" with those (ending with a "_") that use a user-defined abstraction "forofG", you'll see a lot of syntax noise, even worse than with the old long-hand "function" - in terms of making functional abstraction readable, this is going in the wrong direction, opposite to arrow functions.

- I haven't yet figured out how to end an outer generator early
from within a "yield*" nested one (as needed for "take_"), without replacing "yield*" with a micro-interpreter. That might just be
   my incomplete reading of the draft spec, though?

Methods can be replaced by built-ins. It is the reverse that
is now broken.

Au contraire, but it requires a re-think on your part: favor external
iterators over internal iterators.

My main tool for building abstractions are functions. Generators
won't play with functional abstraction. Instead, they force me to use
generator abstraction. Using manual iterators instead exposes the low-level protocol details.

I'm not disputing that generators/iterators are more general/
flexible than array iteration methods. I don't know how to combine
generators with functional abstraction and composition, so my
normal means of building higher-level abstractions are broken.

Perhaps I'm just not using the tools available correctly. Perhaps you could start convincing me by showing me a "forof" implementation that can replace the "for-of" built-in, for all loop bodies?

Claus


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

Reply via email to