ES7 Array and Generator comprehensions

This commit is contained in:
Mahdi Dibaiee 2015-06-08 20:13:38 +04:30
parent 201586b097
commit 710a8d43cd
3 changed files with 18 additions and 10 deletions

View File

@ -8,10 +8,12 @@ categories: es7, generator, array
Array comprehension is a new feature proposed for ES7, with a new syntax
to create new arrays from existing [iterables](http://www.2ality.com/2015/02/es6-iteration.html),
comprehensions can replace `map`, `filter`.
comprehensions can replace map and filter.
Generator comprehension brings the same feature to generators, this is a more
significant change as it removes the need to write new generators for simple map / filter operations.
useful feature as it removes the need to write new generators for simple map/filter operations.
Generator comprehensions allow us to easily write single-line generators, which can replace our arrays in some situations, you might ask why we might consider replacing arrays with generators, the most important reason is their [laziness](#laziness). I've explained laziness later in the article.
Comprehensions are currently only supported by Firefox, use Firefox 30+ or [Babel](https://babeljs.io/repl/) to run the examples. The Node.js version using generator `function* ()`s is available at the [repository](https://github.com/mdibaiee/array-vs-generator) (doesn't require transpilation, use latest node).
@ -25,7 +27,7 @@ let numbers = [1,2,3,4,5];
let even = [ for (n of numbers) if (n % 2 === 0) n ];
// equivalent:
// let even = numbers.map(n => { if (n % 2 === 0) return n });
// let even = numbers.filter(n => n % 2 === 0);
console.log(...even); // 2 4
{% endhighlight %}
@ -40,7 +42,7 @@ let generator = function* () {
}
let squared = ( for (n of generator()) n * n );
// equivalent (not lazy):
// equivalent:
// let squared = Array.from(generator()).map(n => n * n);
console.log(...squared); // 0 1 4 9 16 25
@ -63,7 +65,7 @@ let after = function* (number) {
}
}
// for each number of 0...5, yield an array of 3 numbers after it
// for each number 0...5, yield an array of 3 numbers after it
let nested = ( for (n of generator())
[ for (i of after(n)) i ]
)
@ -77,7 +79,7 @@ console.table(Array.from(nested));
// 6, 7, 8
{% endhighlight %}
#Lazy
#Laziness
This is one of the most important advantages of generators over arrays and things alike.
The reason why I'm including this here is to give you a good reason to write generators instead of arrays
while generator comprehensions make it extremely easy to write them — this is a proof of their usefulness.
@ -106,27 +108,32 @@ temporary arrays in memory? Can we get the first number directly without consumi
Yes, using generators, Look at this:
{% highlight javascript %}
let bigArray = function* () {
let bigGenerator = function* () {
for (let i = 0; i < 100000; i++) {
yield i;
}
}
let squared = ( for (n of bigArray()) n * n );
let squared = ( for (n of bigGenerator()) n * n );
console.log(squared.next());
{% endhighlight %}
Let's see what happens in this case.
Here, we create a generator which will yield numbers 0...100000, nothing is actually allocated or evaluated, we just have a generator which will return a new number every time we call `next()`.
Then we use generator comprehension to create another generator which squares the numbers our `bigArray()` generator yields, again, we don't evaluate or allocate anything, we just create a generator which will call another generator's `next()` method, square the results, and yield it.
Then we use generator comprehension to create another generator which squares the numbers our `bigGenerator()` yields, again, we don't evaluate or allocate anything, we just create a generator which will call another generator's `next()` method, square the results, and yield it.
Now when we call `squared.next()`, the `squared` generator calls `bigArray().next()`, squares the results and yields it, it doesn't do any unnecessary work, it's lazy.
[
![Generator diagram](/img/generator-diagram.png)
](/img/generator-diagram.png)
{% include caption.html text='Calling squared.next() 4 times' %}
If you profile heap/memory usage and running time, you will see the difference.
I have prepared a Node.js version of the test case. With the help of [`process.memoryUsage()`](https://nodejs.org/api/process.html#process_process_memoryusage) and [`console.time`](https://developer.mozilla.org/en-US/docs/Web/API/Console/time) we can easily see the difference.
It's about ten times faster with two times less space used, isn't that awesome?
It's a lot faster, with less space required, isn't that awesome?
[Repository: mdibaiee/array-vs-generator](https://github.com/mdibaiee/array-vs-generator)
@ -135,6 +142,7 @@ It's about ten times faster with two times less space used, isn't that awesome?
If you want to know more about lazy iterators, I recommend raganwald's [Lazy Iterables in JavaScript](http://raganwald.com/2015/02/17/lazy-iteratables-in-javascript.html).
More:
[MDN: Array Comprehensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Array_comprehensions)
[MDN: Generator Comprehensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Generator_comprehensions)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 92 KiB

BIN
img/generator-diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB