Showing posts with label generator. Show all posts
Showing posts with label generator. Show all posts

Wednesday, October 7, 2015

Future JavaScript - Generator Comprehensions

A comprehension generator?
If you enjoyed my look ahead at a JavaScript feature of the future on Monday, then you're in luck. If not, well, please read the rest of this post anyway.

That's right, I'm going to talk about one more upcoming language feature likely to be part of the ECMAScript 7 specification. If you like generator functions and you like array comprehensions, you're going to love Generator Comprehensions!

Maybe.

Not just another weird mashup, Generator Comprehensions have a syntax almost identical to Array Comprehensions, but create simple generator functions based on iterable objects instead of new arrays. Generator functions, if you recall, are a new type of JavaScript function which exit and re-enter while maintaining their state. If your generator function needs are very simple, creating one with a comprehension may be desirable.

Take a look at the simplest of examples to understand how they work. Important note! Just like with Monday's Array Comprehension examples, these only work on Firefox 30+. Sorry Chrome/Node fans.

var data = [ 1, 2, 3, 4, 5 ];

var gen = ( for (n of data) n * 2 );

console.log(gen.next().value); // prints 2
console.log(gen.next().value); // prints 4
console.log(gen.next().value); // prints 6

The Generator Comprehension appears on line 3. It looks almost exactly like an array comprehension, but uses parentheses ( ) instead of square braces [ ] to delineate it. Inside the parentheses, it takes the form for (n of iterable) statement, where the iterable in this case is an array and the statement is multiplying n by 2. The generator created will yield the result of the statement with the next value of n each time it is invoked. It is equivalent to this code:

var data = [ 1, 2, 3, 4, 5 ];

function* doubler(a) {
    for (var i = 0; i < a.length; i++) {
        yield a[i] * 2;
    }
}

var gen = doubler(data);

console.log(gen.next().value); // prints 2
console.log(gen.next().value); // prints 4
console.log(gen.next().value); // prints 6

I suppose you'd have to agree that the comprehension version saves a few lines of code. And fewer lines means fewer chances to make a mistake, right? Maybe. But remember, if you overuse all this slick, super-short syntax, your code becomes less readable and therefore less maintainable. Trade-offs.

I should also mention that just like Array Comprehensions, the statement part of the expression can include an if. So if you wanted a generator that produced just the leap years from a given array of years, it might look like this:

var years = [ 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 ];

var leapYears = ( for (y of years) if ( y % 4 == 0 && (y % 100 != 0 || (y % 100 == 0 && y % 400 == 0)) ) y );

console.log(leapYears.next().value); // prints 1996
console.log(leapYears.next().value); // prints 2000
console.log(leapYears.next().value); // prints 2004

Of course, cramming the leap year logic (divisible by 4 but not divisible by 100 unless also divisible by 400) into a single if statement just trades vertical code for horizontal code - and horizontal code really bothers me. Call me old-fashioned, but I like it when the lines are no more than 80 characters wide. But everyone is entitled to their own code style - unless you have to work on a team! Then you better do what's agreed upon by the group. Just sayin'.

Now for some commentary. While Generator Comprehensions are a fine way to create generators in a single line of code, I feel like the real power of generator functions is in having them encapsulate logic that can not (or should not) be expressed as a single line. If I just want to iterate over an array and even perform some basic translation of its values, I'm not sure that a Generator Comprehension would be my first choice. At the risk of sounding like a software dinosaur, there's nothing wrong with for loops when done correctly. Their intent is more clear, which makes maintenance less costly. I worry about those kinds of things because I'm a software engineer, not a hacker.

I'm also an author of frog comics, which means I worry about you clicking on the link below.

Amphibian.com comic for 7 October 2015

Friday, September 11, 2015

Generator Functions - New in Node 4.0.0

On Wednesday I wrote about arrow functions, a more concise syntax for anonymous functions that is now available in Node 4.0.0. But the new Node has many more new features! Another of the most interesting and useful additions is generator functions.

Generator functions are special functions which can be exited and then re-entered while maintaining their context. That means their variable bindings are saved across entrances. Their name comes from the ability to use these functions to produce sequences of values, like an Iterator in Java.

Generator functions are declared with the special function* syntax, and contain in their bodies a yield statement. Calling a generator function does not execute the body, but instead returns an iterator object which can be used to invoke the body. The statements in the body will be executed until the yield is reached, at which point control returns to the caller.

Consider this example code, which uses a generator function to print out the first 20 numbers in the Fibonacci sequence.

function* fibonacci(){
    var a = 0;
    var b = 1;
    var c = 1;
    while(true) {
        yield c;
        c = a + b;
        a = b;
        b = c;
    }
}

var gen = fibonacci();

for (var i = 0; i < 20; i++) {
    console.log(gen.next().value);
}

It might not be the most efficient way to generate Fibonacci numbers, but it's a good illustration of a generator. When gen.next() is called for the first time, the fibonacci function body is executed up to the yield statement on line 6. All subsequent calls to gen.next() pick up at the next line (seven), which happens to be inside the while loop. After changing the values of the internal variables, we're back to the yield again. This sequence of steps repeats every time gen.next() is called.

As you may notice, calling .next() actually returns on object with a value property. The value property contains whatever was yielded.

Generator functions can come in handy, but since they are part of the ECMAScript 2015 standard, or ES6, they aren't universally available yet. They've just come to Node. Use in the browser is limited to the latest Chrome and Firefox, which means that you're probably going to want to limit yourself to the server-side if you intend to incorporate them in your code.

But as my frogs have learned, being on the cutting edge can be less dangerous than living in the past.

Amphibian.com comic for 11 September 2015