Showing posts with label future. Show all posts
Showing posts with label future. 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

Monday, October 5, 2015

Future JavaScript - Array Comprehensions

I can't wait any longer!
I've spent a bit of time lately writing about new ES6 language features now available in Node 4, but JavaScript doesn't end there. The ECMAScript 7 specification will probably be out next year and should include even more wonderful things. I thought I'd take a look ahead today at the one I'm most excited about, Array Comprehensions.

An Array Comprehension, a feature already available in other languages, is just an easy way to create a new array from an existing one. JavaScript gives us other ways of doing that, such as map, but comprehensions provide a very tight and sensible syntax for a specific job.

Consider the following example. If you want to run this yourself, you'll need Firefox 30+. It is supported neither by other browsers nor Node at this time.

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

var b = [ for (n of a) if (n % 2 !== 0) n ];

console.log(b); // prints [ 1, 3, 5, 7, 9 ]

The array comprehension syntax can be see on line 3, creating a new array b containing just the odd numbers in array a. I like the syntax because the end caps [ ] are square braces, just like an array. Since a new array is the result of the expression, it makes sense. Inside the braces, it is very simple. You have for (n of a), which is the standard iteration syntax. The next part of the expression will be evaluated for each member of a, and for each evaluation n will be the current member. Whatever this expression is will be the value added to the created array. In this example, it will be only values of n which are odd. But consider this example.

var c = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

var d = [ for (n of c) n * n ];

console.log(d); // prints [ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]

No ifs here, the new array will just contain the square of each number in the original array. But you don't even need to care about what's in the original array, technically speaking. Look at this.

var e = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

var f = [ for (n of e) "nothing" ];

console.log(f); // prints a lot of "nothing"

Rather useless, that one. But you could call a function instead...

function doSomething(p) {
    if (p % 2 === 0) {
        return p * p;
    } else {
        return p * 2;
    }
}

var x = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

var y = [ for (n of x) doSomething(n) ];

console.log(y); // prints [ 2, 4, 6, 16, 10, 36, 14, 64, 18, 100 ]

Here's a catch, though. What if you want to zero-out the even numbers from the first array? So instead of returning a new array with just the odds, returning a new array with odds and zeros in place of the evens. You can't use the if...else syntax inside the braces.

var g = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

// this form does not work (invalid syntax):
var h = [ for (n of g) if (n % 2 !== 0) { n } else { 0 } ];

Instead, you have to either call a function or make it a single statement using the shorthand syntax.

var g = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

// this form works:
var h = [ for (n of g) (n % 2 !== 0 ? n : 0) ];
console.log(h); // prints [ 1, 0, 3, 0, 5, 0, 7, 0, 9, 0 ]

// so does this:
function zeroEvens(n) {
    if (n % 2 === 0) {
        return 0;
    } else {
        return n;
    }
}
var j = [ for (n of g) zeroEvens(n) ];
console.log(j); // prints [ 1, 0, 3, 0, 5, 0, 7, 0, 9, 0 ]

Lots of fun, am I right? Well, it's too bad we'll have to wait quite some time to have this much fun in production. Unless you are writing JavaScript for a 100% Firefox environment, you'll have to be patient. It's unclear when ES7 will be official and be supported by other browsers and some future version of Node. Maybe next year sometime? Anyway, this gives you something to look forward to. Like how you look forward to new Amphibian.com comics.

Amphibian.com comic for 5 October 2015