diff --git a/_posts/2015-06-06-array-generator-comprehensions.md b/_posts/2015-06-06-array-generator-comprehensions.md index 42b3ce3..79cc474 100644 --- a/_posts/2015-06-06-array-generator-comprehensions.md +++ b/_posts/2015-06-06-array-generator-comprehensions.md @@ -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) diff --git a/img/array-vs-generator.png b/img/array-vs-generator.png index 03dce02..cd0601f 100644 Binary files a/img/array-vs-generator.png and b/img/array-vs-generator.png differ diff --git a/img/generator-diagram.png b/img/generator-diagram.png new file mode 100644 index 0000000..a05ad05 Binary files /dev/null and b/img/generator-diagram.png differ