250 lines
17 KiB
HTML
250 lines
17 KiB
HTML
|
<!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>. I’ve 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> (doesn’t 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 => 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"><</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 => 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"><</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"><</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 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.</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"><</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">=></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 it’s 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"><</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>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 <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 don’t evaluate or allocate anything, we just create a generator which will call another generator’s <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 doesn’t do any unnecessary work, it’s 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.
|
|||
|
It’s a lot faster, with less space required, isn’t 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 raganwald’s <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&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&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>
|