-
-
Save getify/9105362 to your computer and use it in GitHub Desktop.
// This example shows a simple data structure object that holds | |
// a sequence of numbers from 0 to 99. It defines its own iterator | |
// which lets you dump all those values out using a `for-of` loop. | |
// | |
// Note: the iterator accepts a value to `next(..)` which represents | |
// a step value, if you wanted to get only every nth value from | |
// the sequence. | |
// | |
// Unfortunately, ES6's for-of doesn't have a syntax for passing in | |
// a value to that default per-iteration `next(..)` call that occurs. :( | |
var myNumbers = (function(){ | |
var d = []; | |
for (var i=0; i<100; i++) { | |
d.push(i); | |
} | |
var publicAPI = {}; // no need for an API, can only be iterated on! | |
Object.defineProperty(publicAPI,Symbol.iterator,{ | |
configurable: false, | |
enumerable: false, | |
writable: false, | |
value: function() { | |
var idx = -1; | |
var data = d.slice(); // make a temp copy of internal data so we can iterate on it! | |
return { | |
// step would let you get only every nth number | |
next: function(step) { | |
idx += (+step || 1); | |
var v = data[idx]; | |
return { done: (idx >= data.length), value: v }; | |
} | |
}; | |
} | |
}); | |
return publicAPI; | |
})(); |
// Here's just the standard ES6 `for-of` iteration, which | |
// could only just get out all numbers, not every nth number | |
// in the sequence. :( | |
for (var v of myNumbers) { | |
console.log(v); | |
} | |
// prints out all 100 numbers 0..99 |
// Here's an exploration of a rough possible syntax extension to `for-of` | |
// that would let you pass in a value to each iteration's `next(..)`, | |
// which would let you get out every nth number of the number sequence. | |
// | |
// The `:3` syntax would specify a value that should be passed in to every | |
// `next(..)` call of the iterator. | |
for (var v of myNumbers:3) { | |
console.log(v); | |
} | |
// prints out every 3rd number: 2, 5, 8, ... |
// Here's showing the syntax could take a variable for the `next(..)` | |
// argument, instead of a fixed number, so we can do a stepped-iteration. | |
var step = 1; | |
for (var v of myNumbers:step) { | |
console.log(v); | |
step++; // increase the next step-value each time | |
} | |
// prints out 0, 2, 5, 9, 14, 20, ... | |
// | |
// step of `1` yields `0` | |
// step of `2` yields `2` | |
// step of `3` yields `5` | |
// step of `4` yields `9` | |
// step of `5` yields `14` | |
// step of `6` yields `20` | |
// ... |
// Here's I show how, without the `for-of` syntax I propose, | |
// you have to do the stepped-iteration manually. :( | |
for (var step = 1, it = myNumbers[Symbol.iterator](), ret; | |
// call `next(..)` on the number sequence's iterator | |
!(ret && ret.done) && (ret = it.next(step)); | |
) { | |
console.log(ret.value); | |
step++; | |
} | |
// prints out 0, 2, 5, 9, 14, 20, ... | |
// | |
// step of `1` yields `0` | |
// step of `2` yields `2` | |
// step of `3` yields `5` | |
// step of `4` yields `9` | |
// step of `5` yields `14` | |
// step of `6` yields `20` | |
// ... |
@bmeck i think you might have missed the detail that my use-case actually calls for passing in a new value to each iteration's next, not just an initial value. @BrendanEich missed that on twitter, too, I believe. :)
updated, as well as the 2nd argument to compose being able to be a generator. updated gist for better non-generator support.
@bmeck also, comparing your code to my ugly, manual hack code here: https://gist.github.com/getify/9105362/#file-gistfile5-js Mine seems way way nicer. ;-)
@getify our semantics are slightly different in the above example. I am dueling generators while you are providing values. a closer approx to yours is (also guard your first .next call [ https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume ] ):
function* compose(gen, valueProviderFn) {
var result;
// we need to start up the generator because... first .next() must be empty... dumb
var gen = gen[Symbol.iterator]();
try {result=gen.next(valueProviderFn());}
catch (e) {result=gen.next();}
while (!result.done) {
yield result.value;
result = gen.next(valueProviderFn());
}
return result.value;
}
function* gen() {
var _1 = yield '_';
var _2 = yield ('_1=' + _1);
var _3 = yield ('_2=' + _2);
var _4 = yield ('_3=' + _3);
var _5 = yield ('_4=' + _4);
var _6 = yield ('_5=' + _5);
return '_6=' + _6;
}
function mutableClosure(init) {
var myVar = init;
var fn = function () {
return myVar;
};
fn.set = function (v) {
myVar = v;
}
return fn;
}
var i = 0;
var mutable = mutableClosure(i);
for(var x of compose(gen(),mutable)) {
mutable.set(++i);
console.error('iteration', x);
}
quick and sloppy:
var myNumber = ...
var publicAPI = {
step(by) {return this.varStep(()=>by)},
[Symbol.iterator] () {return this.varStep(()=>1)},
*varStep(stepper) {for (let idx=0; iidx < data.length; idx+= stepper()) yield data[idx]]}
};
...
})();
for (let v of myNumbers) console.log(v)
for (let v of myNumbers.step(3)) console.log(v)
{
let inc = 1;
for (let v of myNumbers.stepper( ()=>inc) {
console.log(v);
inc ++;
}
updated to have your myNumbers function be what is being iterated upon. I would not combine these into a single object since that leads to composition problems, use something like this compose function.
function* compose(gen, valueProviderFn) {
var result;
// we need to start up the generator because... first .next() must be empty... dumb
var gen = gen[Symbol.iterator]();
try {result=gen.next(valueProviderFn());}
catch (e) {result=gen.next();}
while (!result.done) {
yield result.value;
result = gen.next(valueProviderFn());
}
return result.value;
}
var myNumbers = (function(){
var d = [];
for (var i=0; i<100; i++) {
d.push(i);
}
var publicAPI = {}; // no need for an API, can only be iterated on!
Object.defineProperty(publicAPI,Symbol.iterator,{
configurable: false,
enumerable: false,
writable: false,
value: function() {
var idx = -1;
var data = d.slice(); // make a temp copy of internal data so we can iterate on it!
return {
// step would let you get only every nth number
next: function(value) {
console.log('value passed in', value);
idx += (+value || 1);
var v = data[idx];
return { done: (idx >= data.length), value: v };
}
};
}
});
return publicAPI;
})();
var numbers = myNumbers;
// sends in the value of the mutable closure every iteration
for(var x of compose(numbers, function () {return 3})) {
console.error('iteration', x);
}
@allenwb hmm, very interesting. thanks so much for the thoughtful post. i'll see if i can make something like that work in my real use-case.
still wish we had a direct syntax for it, b/c as I showed in the 5th snippet, it IS possible to do it, just dislike having to try harder for seemingly simple features.
obviously, your's is better since it actually uses the for-of
instead of just a for;;
like mine. the third example feels weird to have to use a function-closure to transport value(s) into the iteration machinery at each step, but it might just be the best I can do. :)
composition functions can help with this. However, you still have to
try
/catch
the generator if it was newborn.