emacs.d/clones/lisp/stevelosh.com/blog/2018/05/fun-with-macros-gathering/index.html

432 lines
33 KiB
HTML
Raw Normal View History

2022-10-07 15:47:14 +02:00
<!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>Fun with Macros: Gathering / 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'>Fun with Macros: Gathering</a></h1><p class='date'>Posted on May 21st, 2018.</p><p>I haven't written anything in a while. But after seeing the <a href="https://www.youtube.com/watch?v=dw-y3vNDRWk">metaprogramming
video on Computerphile</a> the other
day I felt the urge to write about Lisp again, so I decided to do a small series
of posts on fun/useful little Common Lisp macros I've made over the past couple
of years.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-the-macro">The Macro</a></li><li><a href="index.html#s2-notes">Notes</a><ol><li><a href="index.html#s3-default-let-bindings">Default LET Bindings</a></li><li><a href="index.html#s4-hygiene">Hygiene</a></li><li><a href="index.html#s5-data-structures">Data Structures</a></li><li><a href="index.html#s6-dynamic-extent">Dynamic Extent</a></li></ol></li><li><a href="index.html#s7-examples">Examples</a><ol><li><a href="index.html#s8-pandigitals">Pandigitals</a></li><li><a href="index.html#s9-hash-table-contents">Hash Table Contents</a></li><li><a href="index.html#s10-triangle-generation">Triangle Generation</a></li></ol></li></ol>
<h2 id="s1-the-macro"><a href="index.html#s1-the-macro">The Macro</a></h2>
<p>The first macro we'll look at is <code>gathering</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> gathering <span class="paren2">(<span class="code">&amp;body body</span>)</span>
<span class="string">&quot;Run `body` to gather some things and return a fresh list of them.
`body` will be executed with the symbol `gather` bound to a
function of one argument. Once `body` has finished, a list of
everything `gather` was called on will be returned.
It's handy for pulling results out of code that executes
procedurally and doesn't return anything, like `maphash` or
Alexandria's `map-permutations`.
The `gather` function can be passed to other functions, but should
not be retained once the `gathering` form has returned (it would
be useless to do so anyway).
Examples:
(gathering
(dotimes (i 5)
(gather i))
=&gt;
(0 1 2 3 4)
(gathering
(mapc #'gather '(1 2 3))
(mapc #'gather '(a b)))
=&gt;
(1 2 3 a b)
&quot;</span>
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code">result</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">,result nil</span>)</span></span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">flet</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">gather <span class="paren1">(<span class="code">item</span>)</span>
<span class="paren1">(<span class="code">push item ,result</span>)</span>
item</span>)</span></span>)</span>
,@body</span>)</span>
<span class="paren4">(<span class="code">nreverse ,result</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<h2 id="s2-notes"><a href="index.html#s2-notes">Notes</a></h2>
<p>As the docstring mentions, sometimes you'll encounter procedural code that
iterates over things but doesn't return any results (CL's <code>maphash</code> and
Alexandria's <code>map-permutations</code> are two examples). The <code>gathering</code> macro
provides an easy way to plug into the guts of the iteration and get a list back.
The docstring describes how to use the macro, but there's a couple of extra
things to note before we move on.</p>
<h3 id="s3-default-let-bindings"><a href="index.html#s3-default-let-bindings">Default LET Bindings</a></h3>
<p>I'm aware that the <code>(let ((,result nil)) ...)</code> in the macro could be simplified
to <code>(let (,result) ...)</code> because bindings without an initial value default to
<code>nil</code>. I just personally dislike that style and prefer to be explicit about the
initial values, even when they're <code>nil</code>.</p>
<h3 id="s4-hygiene"><a href="index.html#s4-hygiene">Hygiene</a></h3>
<p>The <code>flet</code>ed function that actually does the gathering is named as the symbol
<code>gather</code>, not a gensym. Some folks will dislike that because it captures the
function name. I don't mind it. I consider that behavior part of the
intended/documented API of the macro, and because the symbol <code>gather</code> is coming
from the macro's package you'll need to import it (or package qualify it) to
access it. </p>
<p>If you want to provide a bit more safety here you could potentially define
a vanilla function for <code>gather</code> like this:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> gather <span class="paren2">(<span class="code">value</span>)</span>
<span class="string">&quot;Gather `value` into the result. Must be called from inside `gathering`.&quot;</span>
<span class="paren2">(<span class="code">error <span class="string">&quot;GATHER be called from within a GATHERING macro.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p>Doing this would mean calling <code>gather</code> outside a <code>gathering</code> block would signal
an error, and someone defining their <em>own</em> <code>gather</code> function (which could
accidentally get shadowed by the macro's <code>flet</code> later) would get a warning about
redefining the function, which would hopefully make them realize the potential
conflict earlier.</p>
<p>Of course you could also write your own version of <code>gathering</code> that takes in
a symbol to use for the function, if you prefer. That would avoid all of these
issues, at the cost of making every <code>(gathering ...)</code> slightly more verbose.</p>
<h3 id="s5-data-structures"><a href="index.html#s5-data-structures">Data Structures</a></h3>
<p>My actual implementation of this macro uses a simple queue data structure
instead of a list to avoid having to reverse the result at the end, but I didn't
want to include an extra dependency just for this blog post.</p>
<h3 id="s6-dynamic-extent"><a href="index.html#s6-dynamic-extent">Dynamic Extent</a></h3>
<p>Feel free to skip this section. It gets a little bit into the weeds.</p>
<p>One potential optimization we could make is to declare the <code>gather</code> function to
have <a href="http://clhs.lisp.se/Body/d_dynami.htm">dynamic extent</a>. This means that some implementations (e.g. SBCL) <a href="http://www.sbcl.org/manual/#Dynamic_002dextent-allocation">can
stack allocate the closure</a> and save a heap allocation.</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> gathering-dynamic-extent <span class="paren2">(<span class="code">&amp;body body</span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code">result</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">,result nil</span>)</span></span>)</span>
<span class="paren4">(<span class="code"><i><span class="symbol">flet</span></i> <span class="paren5">(<span class="code"><span class="paren6">(<span class="code">gather <span class="paren1">(<span class="code">item</span>)</span>
<span class="paren1">(<span class="code">push item ,result</span>)</span>
item</span>)</span></span>)</span>
<span class="paren5">(<span class="code">declare <span class="paren6">(<span class="code">dynamic-extent #'gather</span>)</span></span>)</span> <span class="comment">; NEW
</span> ,@body</span>)</span>
<span class="paren4">(<span class="code">nreverse ,result</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Whether this matters much depends on the usage patterns. If you use <code>gathering</code>
with some code that typically gathers no elements (and only occasionally gathers
a few), you won't have to heap allocate <em>anything</em> for the common cases, and
things will be a little more efficient. If you typically gather a bunch of
things then you're heap allocating all the cons cells anyway, so allocating the
closure wouldn't be a big deal.</p>
<p>If you want to actually see the stack allocation in action, it might be a little
trickier than you think. SBCL is pretty smart about inlining things, so it
might not allocate the closure at runtime <em>at all</em>, even <em>without</em> the
<code>dynamic-extent</code> declaration:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">disassemble <span class="paren2">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren3">(<span class="code"></span>)</span>
<span class="paren3">(<span class="code">gathering
<span class="paren4">(<span class="code">mapc #'gather '<span class="paren5">(<span class="code">1 2 3</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="comment">; disassembly for (LAMBDA ())
</span><span class="comment">; Size: 149 bytes. Origin: #x10040B8340
</span><span class="comment">; 40: 498B4C2460 MOV RCX, [R12+96] ; no-arg-parsing entry point
</span> <span class="comment">; thread.binding-stack-pointer
</span><span class="comment">; 45: 48894DF8 MOV [RBP-8], RCX
</span><span class="comment">; 49: BB17001020 MOV EBX, #x20100017 ; NIL
</span><span class="comment">; 4E: 488B0D9BFFFFFF MOV RCX, [RIP-101] ; '(1 2 3)
</span><span class="comment">; 55: EB4D JMP L3
</span><span class="comment">; 57: 660F1F840000000000 NOP
</span><span class="comment">; 60: L0: 4881FA17001020 CMP RDX, #x20100017 ; NIL
</span><span class="comment">; 67: 744A JEQ L4
</span><span class="comment">; 69: 488B71F9 MOV RSI, [RCX-7]
</span><span class="comment">; 6D: 49896C2440 MOV [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; 72: 4D8B5C2418 MOV R11, [R12+24] ; thread.alloc-region
</span><span class="comment">; 77: 498D5310 LEA RDX, [R11+16]
</span><span class="comment">; 7B: 493B542420 CMP RDX, [R12+32]
</span><span class="comment">; 80: 7746 JNBE L5
</span><span class="comment">; 82: 4989542418 MOV [R12+24], RDX ; thread.alloc-region
</span><span class="comment">; 87: L1: 498D5307 LEA RDX, [R11+7]
</span><span class="comment">; 8B: 49316C2440 XOR [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; 90: 7403 JEQ L2
</span><span class="comment">; 92: 0F0B09 BREAK 9 ; pending interrupt trap
</span><span class="comment">; 95: L2: 488972F9 MOV [RDX-7], RSI
</span><span class="comment">; 99: 48895A01 MOV [RDX+1], RBX
</span><span class="comment">; 9D: 488BDA MOV RBX, RDX
</span><span class="comment">; A0: 488B4901 MOV RCX, [RCX+1]
</span><span class="comment">; A4: L3: 488BD1 MOV RDX, RCX
</span><span class="comment">; A7: 8D41F9 LEA EAX, [RCX-7]
</span><span class="comment">; AA: A80F TEST AL, 15
</span><span class="comment">; AC: 74B2 JEQ L0
</span><span class="comment">; AE: 0F0B0A BREAK 10 ; error trap
</span><span class="comment">; B1: 2F BYTE #X2F ; OBJECT-NOT-LIST-ERROR
</span><span class="comment">; B2: 10 BYTE #X10 ; RDX
</span><span class="comment">; B3: L4: 488BD3 MOV RDX, RBX
</span><span class="comment">; B6: B902000000 MOV ECX, 2
</span><span class="comment">; BB: FF7508 PUSH QWORD PTR [RBP+8]
</span><span class="comment">; BE: B8B8733120 MOV EAX, #x203173B8 ; #&lt;FDEFN SB-IMPL::LIST-NREVERSE&gt;
</span><span class="comment">; C3: FFE0 JMP RAX
</span><span class="comment">; C5: 0F0B10 BREAK 16 ; Invalid argument count trap
</span><span class="comment">; C8: L5: 6A10 PUSH 16
</span><span class="comment">; CA: 41BB3000B021 MOV R11D, #x21B00030 ; ALLOC-TO-R11
</span><span class="comment">; D0: 41FFD3 CALL R11
</span><span class="comment">; D3: EBB2 JMP L1
</span>NIL</span></code></pre>
<p>Here SBCL <a href="http://www.lispworks.com/documentation/lw50/CLHS/Body/11_abab.htm">knows what <code>mapc</code> is</a> and expands it inline (notice how
there's no function call to <code>mapc</code>). It then realizes it never needs to
allocate a closure for <code>gather</code> at all, it can just inline that too! All the
allocation stuff in that disassembly is for the <code>push</code>ing of new cons cells (to
convince yourself of this, run <code>(disassemble (lambda (&amp;aux result) (push
1 result) result))</code> and compare the assembly).</p>
<p>But if we make our own version of <code>mapc</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> my-mapc <span class="paren2">(<span class="code"><i><span class="symbol">function</span></i> list</span>)</span>
<span class="paren2">(<span class="code">mapc <i><span class="symbol">function</span></i> list</span>)</span></span>)</span></span></code></pre>
<p>Now we can see a difference, because SBCL won't necessarily know it can
stack allocate the closure unless we tell it. Here's what the disassembly
looks like <em>without</em> the <code>dynamic-extent</code> inside <code>gathering</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">disassemble <span class="paren2">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren3">(<span class="code"></span>)</span>
<span class="paren3">(<span class="code">gathering
<span class="paren4">(<span class="code">my-mapc #'gather '<span class="paren5">(<span class="code">1 2 3</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="comment">; disassembly for (LAMBDA ())
</span><span class="comment">; Size: 212 bytes. Origin: #x1004331A20
</span><span class="comment">; 20: 498B4C2460 MOV RCX, [R12+96] ; no-arg-parsing entry point
</span> <span class="comment">; thread.binding-stack-pointer
</span><span class="comment">; 25: 48894DF8 MOV [RBP-8], RCX
</span><span class="comment">; 29: B817001020 MOV EAX, #x20100017 ; NIL
</span><span class="comment">; 2E: 49896C2440 MOV [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; 33: 4D8B5C2418 MOV R11, [R12+24] ; thread.alloc-region
</span><span class="comment">; 38: 498D5B10 LEA RBX, [R11+16]
</span><span class="comment">; 3C: 493B5C2420 CMP RBX, [R12+32]
</span><span class="comment">; 41: 0F874D010000 JNBE #x1004331B94
</span><span class="comment">; 47: 49895C2418 MOV [R12+24], RBX ; thread.alloc-region
</span><span class="comment">; 4C: 498D5B0F LEA RBX, [R11+15]
</span><span class="comment">; 50: 66C743F14101 MOV WORD PTR [RBX-15], 321
</span><span class="comment">; 56: 488943F9 MOV [RBX-7], RAX
</span><span class="comment">; 5A: 49316C2440 XOR [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; 5F: 7403 JEQ L0
</span><span class="comment">; 61: 0F0B09 BREAK 9 ; pending interrupt trap
</span><span class="comment">; 64: L0: 48895DE8 MOV [RBP-24], RBX
</span><span class="comment">; 68: 49896C2440 MOV [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; 6D: 4D8B5C2418 MOV R11, [R12+24] ; thread.alloc-region
</span><span class="comment">; 72: 498D4B20 LEA RCX, [R11+32]
</span><span class="comment">; 76: 493B4C2420 CMP RCX, [R12+32]
</span><span class="comment">; 7B: 0F8723010000 JNBE #x1004331BA4
</span><span class="comment">; 81: 49894C2418 MOV [R12+24], RCX ; thread.alloc-region
</span><span class="comment">; 86: 498D4B0B LEA RCX, [R11+11]
</span><span class="comment">; 8A: B835020000 MOV EAX, 565
</span><span class="comment">; 8F: 480B042508011020 OR RAX, [#x20100108] ; SB-VM:FUNCTION-LAYOUT
</span><span class="comment">; 97: 488941F5 MOV [RCX-11], RAX
</span><span class="comment">; 9B: 488D058E000000 LEA RAX, [RIP+142] ; = #x1004331B30
</span><span class="comment">; A2: 488941FD MOV [RCX-3], RAX
</span><span class="comment">; A6: 49316C2440 XOR [R12+64], RBP ; thread.pseudo-atomic-bits
</span><span class="comment">; AB: 7403 JEQ L1
</span><span class="comment">; AD: 0F0B09 BREAK 9 ; pending interrupt trap
</span><span class="comment">; B0: L1: 48895905 MOV [RCX+5], RBX
</span><span class="comment">; B4: 488BD1 MOV RDX, RCX
</span><span class="comment">; B7: 488D4424F0 LEA RAX, [RSP-16]
</span><span class="comment">; BC: 4883EC10 SUB RSP, 16
</span><span class="comment">; C0: 488B3DE9FEFFFF MOV RDI, [RIP-279] ; '(1 2 3)
</span><span class="comment">; C7: B904000000 MOV ECX, 4
</span><span class="comment">; CC: 488928 MOV [RAX], RBP
</span><span class="comment">; CF: 488BE8 MOV RBP, RAX
</span><span class="comment">; D2: B838734F20 MOV EAX, #x204F7338 ; #&lt;FDEFN MY-MAPC&gt;
</span><span class="comment">; D7: FFD0 CALL RAX
</span><span class="comment">; D9: 480F42E3 CMOVB RSP, RBX
</span><span class="comment">; DD: 488B5DE8 MOV RBX, [RBP-24]
</span><span class="comment">; E1: 488B53F9 MOV RDX, [RBX-7]
</span><span class="comment">; E5: B902000000 MOV ECX, 2
</span><span class="comment">; EA: FF7508 PUSH QWORD PTR [RBP+8]
</span><span class="comment">; ED: B8B8733120 MOV EAX, #x203173B8 ; #&lt;FDEFN SB-IMPL::LIST-NREVERSE&gt;
</span><span class="comment">; F2: FFE0 JMP RAX
</span>NIL</span></code></pre>
<p>Now we can see it's allocating the closure on the heap (note the
<code>SB-VM:FUNCTION-LAYOUT</code>).</p>
<p>If we add the <code>dynamic-extent</code> back into <code>gathering</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">disassemble <span class="paren2">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren3">(<span class="code"></span>)</span>
<span class="paren3">(<span class="code">gathering-dynamic-extent
<span class="paren4">(<span class="code">my-mapc #'gather '<span class="paren5">(<span class="code">1 2 3</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="comment">; disassembly for (LAMBDA ())
</span><span class="comment">; Size: 136 bytes. Origin: #x10043F46E0
</span><span class="comment">; 6E0: 498B4C2460 MOV RCX, [R12+96] ; no-arg-parsing entry point
</span> <span class="comment">; thread.binding-stack-pointer
</span><span class="comment">; 6E5: 48894DF8 MOV [RBP-8], RCX
</span><span class="comment">; 6E9: 48C745E817001020 MOV QWORD PTR [RBP-24], #x20100017 ; NIL
</span><span class="comment">; 6F1: 488BDC MOV RBX, RSP
</span><span class="comment">; 6F4: 48895DE0 MOV [RBP-32], RBX
</span><span class="comment">; 6F8: 4883EC20 SUB RSP, 32
</span><span class="comment">; 6FC: 4883E4F0 AND RSP, -16
</span><span class="comment">; 700: 488D4C240B LEA RCX, [RSP+11]
</span><span class="comment">; 705: B835020000 MOV EAX, 565
</span><span class="comment">; 70A: 480B042508011020 OR RAX, [#x20100108] ; SB-VM:FUNCTION-LAYOUT
</span><span class="comment">; 712: 488941F5 MOV [RCX-11], RAX
</span><span class="comment">; 716: 488D0583000000 LEA RAX, [RIP+131] ; = #x10043F47A0
</span><span class="comment">; 71D: 488941FD MOV [RCX-3], RAX
</span><span class="comment">; 721: 48896905 MOV [RCX+5], RBP
</span><span class="comment">; 725: 488BD1 MOV RDX, RCX
</span><span class="comment">; 728: 488D4424F0 LEA RAX, [RSP-16]
</span><span class="comment">; 72D: 4883EC10 SUB RSP, 16
</span><span class="comment">; 731: 488B3D38FFFFFF MOV RDI, [RIP-200] ; '(1 2 3)
</span><span class="comment">; 738: B904000000 MOV ECX, 4
</span><span class="comment">; 73D: 488928 MOV [RAX], RBP
</span><span class="comment">; 740: 488BE8 MOV RBP, RAX
</span><span class="comment">; 743: B838734F20 MOV EAX, #x204F7338 ; #&lt;FDEFN MY-MAPC&gt;
</span><span class="comment">; 748: FFD0 CALL RAX
</span><span class="comment">; 74A: 480F42E3 CMOVB RSP, RBX
</span><span class="comment">; 74E: 488B5DE0 MOV RBX, [RBP-32]
</span><span class="comment">; 752: 488BE3 MOV RSP, RBX
</span><span class="comment">; 755: 488B55E8 MOV RDX, [RBP-24]
</span><span class="comment">; 759: B902000000 MOV ECX, 2
</span><span class="comment">; 75E: FF7508 PUSH QWORD PTR [RBP+8]
</span><span class="comment">; 761: B8B8733120 MOV EAX, #x203173B8 ; #&lt;FDEFN SB-IMPL::LIST-NREVERSE&gt;
</span><span class="comment">; 766: FFE0 JMP RAX
</span>NIL</span></code></pre>
<p>Much nicer. However, this optimization comes with a price: safety.</p>
<p>The <code>gather</code> closure should never be called outside of the <code>gathering</code> block
that defines it. As the docstring says: it would be useless to do so anyway,
because the result has already been returned. But what would happen if the user
accidentally calls the closure? Let's try it out, first on the original
version without the <code>dynamic-extent</code> declaration:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defparameter</span></i> <span class="special">*f*</span> nil</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> leak <span class="paren2">(<span class="code"><i><span class="symbol">function</span></i></span>)</span>
<span class="paren2">(<span class="code">setf <span class="special">*f*</span> <i><span class="symbol">function</span></i></span>)</span></span>)</span>
<span class="paren1">(<span class="code">gathering <span class="paren2">(<span class="code">leak #'gather</span>)</span></span>)</span>
<span class="paren1">(<span class="code">funcall <span class="special">*f*</span> 1</span>)</span>
<span class="comment">; =&gt; 1</span></span></code></pre>
<p>Here the closure is heap-allocated, so calling it later is fine (if useless).
But what happens if we try our optimized version?</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">gathering-dynamic-extent <span class="paren2">(<span class="code">leak #'gather</span>)</span></span>)</span>
<span class="paren1">(<span class="code">funcall <span class="special">*f*</span></span>)</span>
<span class="comment">; CORRUPTION WARNING in SBCL pid 19575(tid 0x7fffb6814380):
</span><span class="comment">; Memory fault at 0xffffffff849ff8e7 (pc=0x19ff8df, sp=0x19ff890)
</span><span class="comment">; The integrity of this image is possibly compromised.
</span><span class="comment">; Continuing with fingers crossed.
</span><span class="comment">;
</span><span class="comment">; debugger invoked on a SB-SYS:MEMORY-FAULT-ERROR in thread
</span><span class="comment">; #&lt;THREAD &quot;main thread&quot; RUNNING {1001936BF3}&gt;:
</span><span class="comment">; Unhandled memory fault at #xFFFFFFFF849FF8E7.
</span><span class="comment">;
</span><span class="comment">; restarts (invokable by number or by possibly-abbreviated name):
</span><span class="comment">; 0: [ABORT] Exit debugger, returning to top level.
</span><span class="comment">;
</span><span class="comment">; (SB-SYS:MEMORY-FAULT-ERROR)
</span><span class="comment">; 0]</span></span></code></pre>
<p>Things get even worse if you're brave (foolish) enough to be running with
<code>(declaim (safety 0))</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">gathering-dynamic-extent <span class="paren2">(<span class="code">leak #'gather</span>)</span></span>)</span>
<span class="paren1">(<span class="code">funcall <span class="special">*f*</span></span>)</span>
<span class="comment">; debugger invoked on a SB-KERNEL:FLOATING-POINT-EXCEPTION in thread
</span><span class="comment">; #&lt;THREAD &quot;main thread&quot; RUNNING {1001936BF3}&gt;:
</span><span class="comment">; An arithmetic error SB-KERNEL:FLOATING-POINT-EXCEPTION was signalled.
</span><span class="comment">; No traps are enabled? How can this be?
</span><span class="comment">;
</span><span class="comment">;
</span><span class="comment">; restarts (invokable by number or by possibly-abbreviated name):
</span><span class="comment">; 0: [ABORT] Exit debugger, returning to top level.
</span><span class="comment">;
</span><span class="comment">; (&quot;bogus stack frame&quot;)
</span><span class="comment">; 0]</span></span></code></pre>
<p>The moral of this story is that although we can optimize for a little bit of
speed, it comes at a price that might not be worth paying.</p>
<p>Here's an exercise for you: make the original heap-allocated version signal an
error (with a nice error message) when called outside of its <code>gathering</code> block,
instead of silently doing something useless.</p>
<h2 id="s7-examples"><a href="index.html#s7-examples">Examples</a></h2>
<p>Let's finish up by looking at some places where I've found this macro to be
handy. I won't go into too much depth about the individual pieces of code, but
feel free to ask if you have questions.</p>
<h3 id="s8-pandigitals"><a href="index.html#s8-pandigitals">Pandigitals</a></h3>
<p>First up, an example from my Project Euler <code>utils.lisp</code> file:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> pandigitals <span class="paren2">(<span class="code">&amp;optional <span class="paren3">(<span class="code">start 1</span>)</span> <span class="paren3">(<span class="code">end 9</span>)</span></span>)</span>
<span class="string">&quot;Return a list of all `start` to `end` (inclusive) pandigital numbers.&quot;</span>
<span class="paren2">(<span class="code">gathering
<span class="paren3">(<span class="code">map-permutations
<span class="paren4">(<span class="code"><i><span class="symbol">lambda</span></i> <span class="paren5">(<span class="code">digits</span>)</span>
<span class="comment">;; 0-to-n pandigitals are annoying because we don't want
</span> <span class="comment">;; to include those with a 0 first.
</span> <span class="paren5">(<span class="code">unless <span class="paren6">(<span class="code">zerop <span class="paren1">(<span class="code">first digits</span>)</span></span>)</span>
<span class="paren6">(<span class="code">gather <span class="paren1">(<span class="code">digits-to-number digits</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">irange start end</span>)</span>
<span class="keyword">:copy</span> nil</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>This is a prime example of where Alexandria's <code>map-permutations</code> not returning
anything is annoying, but also shows how <code>gathering</code> provides you a little more
flexibility. Even if <code>map-permutations</code> returned a list of results, we'd still
need to filter out the ones that start with zero. With <code>gathering</code> we can avoid
collecting the unneeded results at all by simply not calling <code>gather</code> on them.</p>
<h3 id="s9-hash-table-contents"><a href="index.html#s9-hash-table-contents">Hash Table Contents</a></h3>
<p>Next is an easy way to convert a hash table to lists of <code>(key value)</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> hash-table-contents <span class="paren2">(<span class="code">hash-table</span>)</span>
<span class="string">&quot;Return a fresh list of `(key value)` elements of `hash-table`.&quot;</span>
<span class="paren2">(<span class="code">gathering <span class="paren3">(<span class="code">maphash <span class="paren4">(<span class="code">compose #'gather #'list</span>)</span> hash-table</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Because <code>gather</code> is a <code>flet</code>ed function we can use it like any other function.
Here we <code>compose</code> it with <code>list</code> and pass it off to <code>maphash</code> (which, for some
reason, doesn't return its results).</p>
<h3 id="s10-triangle-generation"><a href="index.html#s10-triangle-generation">Triangle Generation</a></h3>
<p>We'll end with a triangle-generation function from <a href="https://twitter.com/bit_loom/">my procedural art
bot</a>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> generate-universe-balancing <span class="paren2">(<span class="code">depth</span>)</span>
<span class="paren2">(<span class="code">gathering
<span class="paren3">(<span class="code"><i><span class="symbol">labels</span></i> <span class="paren4">(<span class="code"><span class="paren5">(<span class="code">should-stop-p <span class="paren6">(<span class="code">iteration</span>)</span>
<span class="paren6">(<span class="code">or <span class="paren1">(<span class="code">= depth iteration</span>)</span>
<span class="paren1">(<span class="code">and <span class="paren2">(<span class="code">&gt; iteration 6</span>)</span>
<span class="paren2">(<span class="code">randomp <span class="paren3">(<span class="code">map-range 0 depth
0.0 0.05
iteration</span>)</span>
#'rand</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">recur <span class="paren6">(<span class="code">triangle &amp;optional <span class="paren1">(<span class="code">iteration 0</span>)</span></span>)</span>
<span class="paren6">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren1">(<span class="code">should-stop-p iteration</span>)</span>
<span class="paren1">(<span class="code">gather triangle</span>)</span>
<span class="paren1">(<span class="code">map nil <span class="paren2">(<span class="code">rcurry #'recur <span class="paren3">(<span class="code">1+ iteration</span>)</span></span>)</span>
<span class="paren2">(<span class="code">split-triangle-self-balancing triangle</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren4">(<span class="code">map nil #'recur <span class="paren5">(<span class="code">initial-triangles</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>This is used to generate the triangles for images like this:</p>
<p><a href="../../../../static/images/blog/2018/05/triangles.jpeg"><img src="../../../../static/images/blog/2018/05/triangles.jpeg" alt="Recursive triangles"></a></p>
<p>Using <code>gathering</code> lets me write the recursive generation algorithm in a natural
way, and just plug in a simple <code>(gather triangle)</code> when we finally bottom out at
the base case.</p>
</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>