emacs.d/clones/lisp/stevelosh.com/blog/2016/09/iterate-averaging/index.html
2022-10-07 15:47:14 +02:00

134 lines
No EOL
11 KiB
HTML

<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='../../../../static/css/style.css' rel='stylesheet' type='text/css' /><link href='../../../../static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>Customizing Common Lisp&#039;s Iterate: Averaging / Steve Losh</title></head><body><header><a id='logo' href='https://stevelosh.com/'>Steve Losh</a><nav><a href='../../../index.html'>Blog</a> - <a href='https://stevelosh.com/projects/'>Projects</a> - <a href='https://stevelosh.com/photography/'>Photography</a> - <a href='https://stevelosh.com/links/'>Links</a> - <a href='https://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>Customizing Common Lisp&#039;s Iterate: Averaging</a></h1><p class='date'>Posted on September 20th, 2016.</p><p>When I first started learning Common Lisp, one of the things I learned was the
<a href="http://www.gigamonkeys.com/book/loop-for-black-belts.html">loop macro</a>. <code>loop</code> is powerful, but it's not extensible and <a href="https://stevelosh.com/static/images/blog/2016/09/loop-macro.jpg">some
people find it ugly</a>. The <a href="https://common-lisp.net/project/iterate/">iterate</a> library was made to solve both of
these problems.</p>
<p>Unfortunately I haven't found many guides or resources on how to extend
<code>iterate</code>. The <code>iterate</code> manual describes the macros you need to use, but only
gives a few sparse examples. Sometimes it's helpful to see things in action.</p>
<p>I've made a few handy extensions myself in the past couple of months, so
I figured I'd post about them in case someone else is looking for examples of
how to write their own <code>iterate</code> clauses and drivers.</p>
<p>This entry is the first in a series:</p>
<ul>
<li><a href="index.html">Averaging</a></li>
<li><a href="../../10/iterate-timing/index.html">Timing</a></li>
</ul>
<p>This first post will show how to make a <code>averaging</code> clause that keeps a running
average of a given expression during the loop. I've found it handy in a couple
of places.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-end-result">End Result</a></li><li><a href="index.html#s2-code">Code</a></li><li><a href="index.html#s3-debugging">Debugging</a></li></ol>
<h2 id="s1-end-result"><a href="index.html#s1-end-result">End Result</a></h2>
<p>Before we look at the code, let's look at what we're aiming for:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">iterate <span class="paren2">(<span class="code">for i <span class="keyword">:in</span> <span class="paren3">(<span class="code">list 20 10 10 20</span>)</span></span>)</span>
<span class="paren2">(<span class="code">averaging i</span>)</span></span>)</span>
<span class="comment">; =&gt;
</span>15
<span class="paren1">(<span class="code">iterate <span class="paren2">(<span class="code">for l <span class="keyword">:in</span> '<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">10 <span class="keyword">:foo</span></span>)</span> <span class="paren4">(<span class="code">20 <span class="keyword">:bar</span></span>)</span> <span class="paren4">(<span class="code">0 <span class="keyword">:baz</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code">averaging <span class="paren3">(<span class="code">car l</span>)</span> <span class="keyword">:into</span> running-average</span>)</span>
<span class="paren2">(<span class="code">collect running-average</span>)</span></span>)</span>
<span class="comment">; =&gt;
</span><span class="paren1">(<span class="code">10 15 10</span>)</span></span></code></pre>
<p>Simple enough. The <code>averaging</code> clause takes an expression (and optionally
a variable name) and averages its value over each iteration of the loop.</p>
<h2 id="s2-code"><a href="index.html#s2-code">Code</a></h2>
<p>There's not much code to <code>averaging</code>, but it does contain a few ideas that crop
up often when writing <code>iterate</code> extensions:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro-clause</span></i> <span class="paren2">(<span class="code">AVERAGING expr &amp;optional INTO var</span>)</span>
<span class="string">&quot;Maintain a running average of `expr` in `var`.
If `var` is omitted the final average will be
returned instead.
Examples:
(iterate (for x :in '(0 10 0 10))
(averaging x))
=&gt;
5
(iterate (for x :in '(1.0 1 2 3 4))
(averaging (/ x 10) :into avg)
(collect avg))
=&gt;
(0.1 0.1 0.13333334 0.17500001 0.22)
&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-gensyms</span></i> <span class="paren3">(<span class="code">count total</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">average <span class="paren6">(<span class="code">or var <span class="special">iterate::*result-var*</span></span>)</span></span>)</span></span>)</span>
`<span class="paren4">(<span class="code"><i><span class="symbol">progn</span></i>
<span class="paren5">(<span class="code">for ,count <span class="keyword">:from</span> 1</span>)</span>
<span class="paren5">(<span class="code">sum ,expr <span class="keyword">:into</span> ,total</span>)</span>
<span class="paren5">(<span class="code">for ,average = <span class="paren6">(<span class="code">/ ,total ,count</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>We use <code>defmacro-clause</code> to define the clause. Check <a href="https://common-lisp.net/project/iterate/doc/Rolling-Your-Own.html#Rolling-Your-Own">the iterate manual</a>
to learn more about the basics of that.</p>
<p>The first thing to note is the big docstring, which describes how to use the
clause and gives some examples. I prefer to err on the side of providing <em>more</em>
information in documentation rather than less. People who don't need the
hand-holding can quickly skim over it, but if you omit information it can leave
people confused. <a href="http://www.bash.org/?105201">Your monitor isn't going to run out of ink</a> and <a href="https://steve-yegge.blogspot.is/2008/09/programmings-dirtiest-little-secret.html">you
type fast (right?)</a> so be nice and just write the damn docs.</p>
<p>Next up is selecting the name of the variable for the average. The <code>(or var
iterate::*result-var*)</code> pattern is one I use often when writing <code>iterate</code>
clauses. It's kind of weird that <code>*result-var*</code> isn't external in the <code>iterate</code>
package, but this idiom is explicitly mentioned in the manual so I suppose it's
fine to use.</p>
<p>Finally, we <em>could</em> have written a simpler version of <code>averaging</code> that just
returned the result from the loop:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro-clause</span></i> <span class="paren2">(<span class="code">AVERAGING expr</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">with-gensyms</span></i> <span class="paren3">(<span class="code">count total</span>)</span>
`<span class="paren3">(<span class="code"><i><span class="symbol">progn</span></i>
<span class="paren4">(<span class="code">for ,count <span class="keyword">:from</span> 1</span>)</span>
<span class="paren4">(<span class="code">sum ,expr <span class="keyword">:into</span> ,total</span>)</span>
<span class="paren4">(<span class="code">finally <span class="paren5">(<span class="code">return <span class="paren6">(<span class="code">/ ,total ,count</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>This would work, but doesn't let us see the running average during the course of
the loop. <code>iterate</code>'s built-in clauses like <code>collect</code> and <code>sum</code> usually allow
you to access the &quot;in-progress&quot; value, so it's good for our extensions to
support it too.</p>
<h2 id="s3-debugging"><a href="index.html#s3-debugging">Debugging</a></h2>
<p>This clause is pretty simple, but more complicated ones can get a bit tricky.
When writing vanilla Lisp macros I usually end up writing the macro and then
<code>macroexpand-1</code>'ing a sample of it to make sure it's expanding to what I think
it should.</p>
<p>As far as I can tell there's no simple way to macroexpand an <code>iterate</code> clause on
its own. This is really a pain in the ass when you're trying to debug them, so
I <a href="https://github.com/sjl/cl-losh/blob/55de0419a9b97a35943ce4884b598dbd99cc5670/losh.lisp#L1105-L1151">hacked together</a> a <code>macroexpand-iterate</code> function for my own sanity.
It's not pretty, but it gets the job done:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">macroexpand-iterate '<span class="paren2">(<span class="code">averaging <span class="paren3">(<span class="code">* 2 x</span>)</span></span>)</span></span>)</span>
<span class="comment">; =&gt;
</span><span class="paren1">(<span class="code"><i><span class="symbol">PROGN</span></i>
<span class="paren2">(<span class="code">FOR <span class="keyword">#:COUNT518</span> <span class="keyword">:FROM</span> 1</span>)</span>
<span class="paren2">(<span class="code">SUM <span class="paren3">(<span class="code">* 2 X</span>)</span> <span class="keyword">:INTO</span> <span class="keyword">#:TOTAL519</span></span>)</span>
<span class="paren2">(<span class="code">FOR <span class="special">ITERATE::*RESULT-VAR*</span> = <span class="paren3">(<span class="code">/ <span class="keyword">#:TOTAL519</span> <span class="keyword">#:COUNT518</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code">macroexpand-iterate '<span class="paren2">(<span class="code">averaging <span class="paren3">(<span class="code">* 2 x</span>)</span> <span class="keyword">:into</span> foo</span>)</span></span>)</span>
<span class="comment">; =&gt;
</span><span class="paren1">(<span class="code"><i><span class="symbol">PROGN</span></i>
<span class="paren2">(<span class="code">FOR <span class="keyword">#:COUNT520</span> <span class="keyword">:FROM</span> 1</span>)</span>
<span class="paren2">(<span class="code">SUM <span class="paren3">(<span class="code">* 2 X</span>)</span> <span class="keyword">:INTO</span> <span class="keyword">#:TOTAL521</span></span>)</span>
<span class="paren2">(<span class="code">FOR FOO = <span class="paren3">(<span class="code">/ <span class="keyword">#:TOTAL521</span> <span class="keyword">#:COUNT520</span></span>)</span></span>)</span></span>)</span></span></code></pre>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>