theread.me/site/es7-array-generator-comprehensions/index.html

250 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ES7 Array and Generator comprehensions</title>
<meta name="description" content="Array comprehension is a new feature proposed for ES7, with a new syntaxto create new arrays from existing iterables,comprehensions can replace map and filter.">
<link href="https://fonts.googleapis.com/css?family=Secular+One|Nunito|Mononoki" rel="stylesheet">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://localhost:4000/es7-array-generator-comprehensions/">
<link rel="alternate" type="application/rss+xml" title="mahdi" href="http://localhost:4000/feed.xml" />
<!--<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>-->
<script>
var channel = new BroadcastChannel('egg');
channel.addEventListener('message', message => {
alert('Got a message from the other tab:\n' + message.data);
});
</script>
</head>
<body>
<header class="site-header">
<h1>
<a class='site-title' href='/'>
mahdi
</a>
</h1>
<nav>
<p>
<a href="/snippets">snippets</a>
<a href="/art">pictures</a>
</p>
<!--<p class='categories'>-->
<!---->
<!---->
<!--<a href="">art</a>-->
<!---->
<!---->
<!---->
<!---->
<!--</p>-->
<p>
<a href='mailto:mdibaiee@pm.me'>email</a>
<a href='https://git.mahdi.blog/mahdi'>git</a>
<a href='https://www.librarything.com/profile/mdibaiee'>librarything</a>
<a href="http://localhost:4000/feed.xml">feed</a>
</p>
</nav>
</header>
<div class="page-content">
<div class="wrapper">
<h1 class="page-heading"></h1>
<div class='post lang-en'>
<div class="post-header">
<h1 class="post-title"><p>ES7 Array and Generator comprehensions</p>
</h1>
<p class="post-meta">
<span>Jun 6, 2015</span>
<span>Reading time: 6 minutes</span>
</p>
</div>
<article class="post-content">
<p>Array comprehension is a new feature proposed for ES7, with a new syntax
to create new arrays from existing <a href="http://www.2ality.com/2015/02/es6-iteration.html">iterables</a>,
comprehensions can replace map and filter.</p>
<p>Generator comprehension brings the same feature to generators, this is a more
useful feature as it removes the need to write new generators for simple map/filter operations.</p>
<p>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 <a href="#laziness">laziness</a>. Ive explained laziness later in the article.</p>
<p>Comprehensions are currently only supported by Firefox, use Firefox 30+ or <a href="https://babeljs.io/repl/">Babel</a> to run the examples. The Node.js version of examples using generator <code class="language-plaintext highlighter-rouge">function* ()</code>s is available at the <a href="https://github.com/mdibaiee/array-vs-generator">repository</a> (doesnt require transpilation, use latest node).</p>
<h1 id="syntax">Syntax</h1>
<p>The syntax is pretty simple, you can only use <code class="language-plaintext highlighter-rouge">for of</code> and <code class="language-plaintext highlighter-rouge">if</code> inside comprehensions.</p>
<p>Array comprehensions:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">let</span> <span class="nx">numbers</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">];</span>
<span class="kd">let</span> <span class="nx">even</span> <span class="o">=</span> <span class="p">[</span> <span class="k">for</span> <span class="p">(</span><span class="nx">n</span> <span class="k">of</span> <span class="nx">numbers</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="nx">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="nx">n</span> <span class="p">];</span>
<span class="c1">// equivalent:</span>
<span class="c1">// let even = numbers.filter(n =&gt; n % 2 === 0);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(...</span><span class="nx">even</span><span class="p">);</span> <span class="c1">// 2 4</span></code></pre></figure>
<p>Generator comprehensions:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// yield 0...5</span>
<span class="kd">let</span> <span class="nx">generator</span> <span class="o">=</span> <span class="kd">function</span><span class="o">*</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">yield</span> <span class="nx">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">squared</span> <span class="o">=</span> <span class="p">(</span> <span class="k">for</span> <span class="p">(</span><span class="nx">n</span> <span class="k">of</span> <span class="nx">generator</span><span class="p">())</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">n</span> <span class="p">);</span>
<span class="c1">// equivalent:</span>
<span class="c1">// let squared = Array.from(generator()).map(n =&gt; n * n);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(...</span><span class="nx">squared</span><span class="p">);</span> <span class="c1">// 0 1 4 9 16 25</span></code></pre></figure>
<p>You can also nest comprehensions:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// yield 0...5</span>
<span class="kd">let</span> <span class="nx">generator</span> <span class="o">=</span> <span class="kd">function</span><span class="o">*</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">6</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">yield</span> <span class="nx">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// yield three numbers after number</span>
<span class="kd">let</span> <span class="nx">after</span> <span class="o">=</span> <span class="kd">function</span><span class="o">*</span> <span class="p">(</span><span class="nx">number</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">4</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">yield</span> <span class="nx">number</span> <span class="o">+</span> <span class="nx">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// for each number 0...5, yield an array of 3 numbers after it</span>
<span class="kd">let</span> <span class="nx">nested</span> <span class="o">=</span> <span class="p">(</span> <span class="k">for</span> <span class="p">(</span><span class="nx">n</span> <span class="k">of</span> <span class="nx">generator</span><span class="p">())</span>
<span class="p">[</span> <span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="k">of</span> <span class="nx">after</span><span class="p">(</span><span class="nx">n</span><span class="p">))</span> <span class="nx">i</span> <span class="p">]</span>
<span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">table</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">nested</span><span class="p">));</span>
<span class="c1">// 1, 2, 3</span>
<span class="c1">// 2, 3, 4</span>
<span class="c1">// 3, 4, 5</span>
<span class="c1">// 4, 5, 6</span>
<span class="c1">// 5, 6, 7</span>
<span class="c1">// 6, 7, 8</span></code></pre></figure>
<h1 id="laziness">Laziness</h1>
<p>This is one of the most important advantages of generators over arrays and things alike.
The reason why Im 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.</p>
<p>In programming, laziness means doing nothing until the results are requested or in simpler terms, avoiding unnecessary work.
For example, when you create an array and map it, the result will be evaluated no matter you need it now or not, you need the whole thing or a part of it, etc.</p>
<p>Take this example:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">let</span> <span class="nx">bigArray</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">100000</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">bigArray</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">first</span> <span class="o">=</span> <span class="nx">bigArray</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">n</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">first</span><span class="p">);</span></code></pre></figure>
<p>You know what happens here, first, map is evaluated, returning thousands of squared numbers, then
the first element is returned. We must allocate and evaluate the whole squared array to be able to know about its first or second element.</p>
<p>Think of optimizing it, is it possible to get the desired result without storing
temporary arrays in memory? Can we get the first number directly without consuming a big chunk of memory?</p>
<p>Yes, using generators, Look at this:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">let</span> <span class="nx">bigGenerator</span> <span class="o">=</span> <span class="kd">function</span><span class="o">*</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">yield</span> <span class="nx">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">squared</span> <span class="o">=</span> <span class="p">(</span> <span class="k">for</span> <span class="p">(</span><span class="nx">n</span> <span class="k">of</span> <span class="nx">bigGenerator</span><span class="p">())</span> <span class="nx">n</span> <span class="o">*</span> <span class="nx">n</span> <span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">squared</span><span class="p">.</span><span class="nx">next</span><span class="p">());</span></code></pre></figure>
<p>Lets 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 <code class="language-plaintext highlighter-rouge">next()</code>.
Then we use generator comprehension to create another generator which squares the numbers our <code class="language-plaintext highlighter-rouge">bigGenerator()</code> yields, again, we dont evaluate or allocate anything, we just create a generator which will call another generators <code class="language-plaintext highlighter-rouge">next()</code> method, square the results, and yield it.</p>
<p>Now when we call <code class="language-plaintext highlighter-rouge">squared.next()</code>, the <code class="language-plaintext highlighter-rouge">squared</code> generator calls <code class="language-plaintext highlighter-rouge">bigArray().next()</code>, squares the results and yields it, it doesnt do any unnecessary work, its lazy.</p>
<p><a href="/img/generator-diagram.png">
<img src="/img/generator-diagram.png" alt="Generator diagram" />
</a>
<span class="image-caption">Calling squared.next() 4 times</span></p>
<p>If you profile heap/memory usage and running time, you will see the difference.</p>
<p>I have prepared a Node.js version of the test case. With the help of <a href="https://nodejs.org/api/process.html#process_process_memoryusage"><code class="language-plaintext highlighter-rouge">process.memoryUsage()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Console/time"><code class="language-plaintext highlighter-rouge">console.time</code></a> we can easily see the difference.
Its a lot faster, with less space required, isnt that awesome?</p>
<p><a href="https://github.com/mdibaiee/array-vs-generator">Repository: mdibaiee/array-vs-generator</a></p>
<p><img src="/img/array-vs-generator.png" alt="Array vs Generator performance" /></p>
<p>If you want to know more about lazy iterators, I recommend raganwalds <a href="http://raganwald.com/2015/02/17/lazy-iteratables-in-javascript.html">Lazy Iterables in JavaScript</a>.</p>
<p>More:</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Array_comprehensions">MDN: Array Comprehensions</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Generator_comprehensions">MDN: Generator Comprehensions</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of">MDN: for…of</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators?redirectlocale=en-US&amp;redirectslug=JavaScript%2FGuide%2FIterators_and_Generators">MDN: Iterators and Generators</a></p>
<p><a href="https://hacks.mozilla.org/2015/05/es6-in-depth-generators/?utm_source=javascriptweekly&amp;utm_medium=email">ES6 in Depth: Generators</a></p>
</article>
<div class="share-page">
Share in
<a href="https://twitter.com/intent/tweet?text=ES7 Array and Generator comprehensions&url=http://localhost:4000/es7-array-generator-comprehensions/&via=&related=" rel="nofollow" target="_blank" title="Share on Twitter">Twitter</a>
<a href="https://facebook.com/sharer.php?u=http://localhost:4000/es7-array-generator-comprehensions/" rel="nofollow" target="_blank" title="Share on Facebook">Facebook</a>
<a href="https://plus.google.com/share?url=http://localhost:4000/es7-array-generator-comprehensions/" rel="nofollow" target="_blank" title="Share on Google+">Google+</a>
</div>
<div id="commento"></div>
<script defer
src="//commento.mahdi.blog/js/commento.js">
</script>
<script src="/js/heading-links.js"></script>
</div>
</div>
</div>
</body>
</html>