A syntax alternative for generators
Brendan Eich
brendan at mozilla.com
Wed Sep 12 05:26:29 PDT 2012
Contrived example has contrived error. Sorry, this is really not fairly
argued or evidence-based.
Generators are the easiest way to write an iterator implementation.
Typically iterator implementations are named, reusable. The better way
to write anything like the mutable collection snapshot iteration is
(from my earlier message):
function collectionIterator(aCollection) {
for (let i = 0; i < aCollection.length; i++)
yield aCollection[i];
}
function snapshot(aMutableCollection) {
return collectionIterator(aMutableCollection.clone());
}
But mutable collections can sometimes have smarter lazily snapshotting
iterators, so this is just a sketch. Still this is very much like real
JS built on closures and Prototype- and jQuery-like clone/extend utilities.
Prototype in particular is full of this.each(function(...){...}) and
Object.keys(...).each(function...) uses. No one writes
ad-hoc/non-reusable closure-based iterators, as far as I can see.
s/generator/closure-based/ above makes no difference -- a point I made
earlier. You cannot single out generators here any more than
closure-based iterator factory functions.
/be
Allen Wirfs-Brock wrote:
> oops, I forgot to force calls to the inner function * definitions.
> Another hazard of the plan of record that my alternative proposal
> doesn't have. I fixed occurrence of this bug below:
> On Sep 11, 2012, at 9:42 PM, Allen Wirfs-Brock wrote:
>
>>
>> On Sep 11, 2012, at 7:48 PM, Jason Orendorff wrote:
>>
>>> I'd appreciate a less contrived motivating example. The best way to
>>> implement this dataSnapshot() function is simply:
>>>
>>> function dataSnapshot(aCollection) {
>>> return aCollection.clone();
>>> }
>>>
>>> -j
>>>
>>
>> My original example was something like:
>>
>> function *dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> for (let i=0; i<snapshot.length; i++) yield snapshot[i]
>> }
>>
>> It wasn't actually quite as intentionally clear as the above and
>> that allowed it to evolve within the thread into something that could
>> arguably be restated as you show. But that was why I created the
>> original example.
>>
>> The intent of the original was to show a situation where:
>>
>> * there is a dynamically changing data source you want to analyze
>> * You need to capture it for the analysis while it is in a stable state
>>
>> * The data capture process does not produce a "clone" but a
>> different (perhaps more efficient for readonly access)
>> * You know how to navigate this captured data structure and want to
>> provide a generator-based iteration that a analysis process can
>> you to access its elements
>> * You need to be sure that the data is captured when at the exact
>> point in program where "dataSnapshot" was invoked, not at some
>> arbietrary later time when "next" is first called.
>>
>>
>> I think these requirements are pretty typical of a common situation
>> that occurs when you want to perform stable traversal over
>> dynamically mutable object structures and the example was contrived
>> to show some pitfalls that could occur if you try to do this using a
>> generator.
>>
>> The original example fails for at least the last bullet. To fix
>> that, it would have to be rewritten, something like:
>>
>> function dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> return function *() {
>> for (let i=0; i<snapshot.length; i++) yield snapshot[i]
>> }(); //<--- return generator instance, not the generator constructor
>>
>> }
>>
>> Note that this rewrite eliminates the need for an formal parameters
>> on the actual generator definition.
>>
>> If dataSnapshot was a method on some object and the iteration results
>> depended upon that object, you might have instead originally coded it as:
>>
>>
>> class DataSourceAnalyzer {
>> ... //a constructor and other methods
>> *dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> for (let i=0; i<snapshot.length; i++) yield
>> this.analyzeElement(snapshot[i]); // <--- note use of this
>> }
>> }
>>
>> This also falls the last requirement in the bullet list above. So,
>> you might refactor similarly to what I did for the non-method form:
>>
>> class DataSourceAnalyzer {
>> ... //a constructor and other methods
>> dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> return function *() {
>> for (let i=0; i<snapshot.length; i++) yield
>> this.analyzeElement(snapshot[i]); // <--- note erroneous use of this
>> }(); //<--- return generator instance, not the generator
>> constructor
>> }
>> }
>>
>> and forget that the function * expression uses a dynamic this. If
>> you remembered that you would instead have had to revert to ES<6
>> style this capture:
>>
>> class DataSourceAnalyzer {
>> ... //a constructor and other methods
>> dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> let self = this;
>> return function *() {
>> for (let i=0; i<snapshot.length; i++) yield
>> self.analyzeElement(snapshot[i]); // <--- note use of self instead
>> of this
>> }(); //<--- return generator instance, not the generator
>> constructor
>> }
>> }
>>
>> Using my alternative generator syntax, this would be written as:
>>
>> class DataSourceAnalyzer {
>> ... //a constructor and other methods
>> dataSnapshot(aDataSource) {
>> let snapshot = aDataSource.captureCurrentState();
>> return *=> for (let i=0; i<snapshot.length; i++) yield
>> this(snapshot[i]); // <--- note valid of lexical this
>> }
>> }
>>
>>
>>
>>
>>
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>> https://mail.mozilla.org/listinfo/es-discuss
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
More information about the es-discuss
mailing list