Labels: generator expressions , generators , javascript
One of those new features (an exciting one) is the introduction of generators. In layman's terms generators are: pause and resume for your methods. How is that? A generator is simply a normal method, but one that has the ability to yield back control to the caller while maintaining its state for future runs. This is not a very accurate description as it will not yield back to the method caller but actually to those who call next() on it. Confused already? let's use an example to make things clear.
//fibnacci example, stolen right from the mozilla docs
function fib() {
var i = 0, j = 1;
while (true) {
yield i;
var t = i;
i = j;
j += t;
}
}
var g = fib();
for (var i = 0; i < 10; i++) {
document.write(g.next() + " ");
}
which results in:
1 1 2 3 5 8 13 21 34 55
Before you get lost in the above code, here is a quick description of what happens:
- Javascript knows the above fib function is a generator (becuase it encloses the keyword yield)
- When you call a generator function, any parameters you send in the call are bound
- Rather than executing the method body it returns a generator iterator, one which you can call some of the iterator methods on (like next, send and close)
- The loop outside the function is run and g.next() gets called
- Whenever g.next() is called the fib function body gets executed, until it reaches the yield keword, at this point it returns control back to the caller of next() while its state remains intact.
- The result of the expression following the yield is returned to the caller of next (this is what is being generated by the generator)
- Subsequent calls to next() will cause the function to continue right after the yield keyword and yields control back again when it re-encounters it.
You can think of generators as a interruptable transformations. They are usually used to generate a transformation of some iteratable data while giving the callers control of when (or if) they are allowed to move forward with this generation.
Building on this, a new feature was introduced to make your life even easier. Generator expressions; Instead of having to write a generator functions it is possible to describe your transformation as a short hand in-place expression.
Consider the following generator function (also stolen from Mozilla but modified this time)
function square(obj) {
for each ( var i in obj )
yield i*i;
}
var someNumbers = {a:1,b:2,c:3,d:4,e:5,f:6};
var iterator = square(someNumbers);
try {
while (true) {
document.write(iterator.next() + " ");
}
} catch (error if error instanceof StopIteration) {
//we are done
}
this results in:
1 4 9 16 25 36
This square function will iterate over the hash values (using the for each..in statement) and will generate the square of the current hash value and yield control back to the caller.
In this case the generator function is merely doing a very simple transformation. Thus we can easily replace it by a generator expression.
Like this example (for the third time, stolen and modified from mozilla.org):
var someNumbers = {a:1,b:2,c:3,d:4,e:5,f:6};
var iterator = (i * i for each (i in someNumbers));
try {
while (true) {
document.write(iterator.next() + " ");
}
} catch (error if error instanceof StopIteration) {
//we are done
}
This line :
var iterator = (i * i for each (i in someNumbers));Is what we call generator expressions. This is exactly like the above generator function. It returns an iterator (the assignment) that when its next method is called it does a transformation (the expression i * i) in some sort of a loop (the for each..in statement) and returns control back to the caller after each iteration (implicitly yielding the expression result).
And there is more to generator expressions. They actually have a neat way of yielding only under some condition and the Javascript 1.8 developers (thanks Brendan et. al) came up with a cool Ruby like conditioning.
Say you only wanted to get the squares of the even numbers in the list, the above generator expression will be rewritten as:
var iterator =sweet!
(i * i for each (i in someNumbers) if (i%2==0));