Skip to content

Instantly share code, notes, and snippets.

@getify
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save getify/9105362 to your computer and use it in GitHub Desktop.
Save getify/9105362 to your computer and use it in GitHub Desktop.
Exploring why I wish ES6's `for-of` had a syntax for sending in values to each iteration's `next(..)` call
// 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
Copy link

bmeck commented Feb 20, 2014

composition functions can help with this. However, you still have to try/catch the generator if it was newborn.

function* compose(gen, value) {
  var iter = asIterable(value);
  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(iter.next().value);}
  catch (e) {result=gen.next();}
  while (!result.done) {
    yield result.value;
    result = gen.next(iter.next().value);
  }
  return result.value;
}
function asIterable(v) {
  if (v[Symbol.iterator]) {
    return v;
  }
  return function* () {
    while(true) {yield v;}
  }();
}

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;
}
var myIter = gen();
for(var x of compose(myIter,asIterable(' test'))) {
  console.error('iteration',x)
}

@getify
Copy link
Author

getify commented Feb 20, 2014

@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. :)

@bmeck
Copy link

bmeck commented Feb 20, 2014

updated, as well as the 2nd argument to compose being able to be a generator. updated gist for better non-generator support.

@getify
Copy link
Author

getify commented Feb 20, 2014

@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. ;-)

@bmeck
Copy link

bmeck commented Feb 20, 2014

@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);
}

@allenwb
Copy link

allenwb commented Feb 20, 2014

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 ++;
} 

@bmeck
Copy link

bmeck commented Feb 20, 2014

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);
}

@getify
Copy link
Author

getify commented Feb 20, 2014

@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. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment