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