432 lines
33 KiB
HTML
432 lines
33 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>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">&body body</span>)</span>
|
||
|
<span class="string">"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))
|
||
|
=>
|
||
|
(0 1 2 3 4)
|
||
|
|
||
|
(gathering
|
||
|
(mapc #'gather '(1 2 3))
|
||
|
(mapc #'gather '(a b)))
|
||
|
=>
|
||
|
(1 2 3 a b)
|
||
|
|
||
|
"</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">"Gather `value` into the result. Must be called from inside `gathering`."</span>
|
||
|
<span class="paren2">(<span class="code">error <span class="string">"GATHER be called from within a GATHERING macro."</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">&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 ; #<FDEFN SB-IMPL::LIST-NREVERSE>
|
||
|
</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 (&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 ; #<FDEFN MY-MAPC>
|
||
|
</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 ; #<FDEFN SB-IMPL::LIST-NREVERSE>
|
||
|
</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 ; #<FDEFN MY-MAPC>
|
||
|
</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 ; #<FDEFN SB-IMPL::LIST-NREVERSE>
|
||
|
</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">; => 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">; #<THREAD "main thread" RUNNING {1001936BF3}>:
|
||
|
</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">; #<THREAD "main thread" RUNNING {1001936BF3}>:
|
||
|
</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">; ("bogus stack frame")
|
||
|
</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">&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">"Return a list of all `start` to `end` (inclusive) pandigital numbers."</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">"Return a fresh list of `(key value)` elements of `hash-table`."</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">> 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 &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>
|