564 lines
37 KiB
HTML
564 lines
37 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: If-Let and When-Let / 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: If-Let and When-Let</a></h1><p class='date'>Posted on July 9th, 2018.</p><p>I haven't been writing much lately because I've been in the process of switching
|
||
|
my life over to Linux from OS X. I finally managed to get my blog
|
||
|
infrastructure running again, so let's take a look at some deceptively-tricky
|
||
|
Common Lisp macros: <code>when-let</code> and <code>if-let</code> (and their <code>let*</code> counterparts).</p>
|
||
|
|
||
|
<ol class="table-of-contents"><li><a href="index.html#s1-introduction">Introduction</a></li><li><a href="index.html#s2-a-first-attempt">A First Attempt</a></li><li><a href="index.html#s3-multiple-bindings">Multiple Bindings</a></li><li><a href="index.html#s4-adding-some-stars">Adding Some Stars</a></li><li><a href="index.html#s5-consistency">Consistency</a></li><li><a href="index.html#s6-declarations">Declarations</a></li><li><a href="index.html#s7-result">Result</a></li></ol>
|
||
|
|
||
|
<h2 id="s1-introduction"><a href="index.html#s1-introduction">Introduction</a></h2>
|
||
|
|
||
|
<p>I first heard of the <a href="https://clojuredocs.org/clojure.core/when-let"><code>when-let</code></a> and
|
||
|
<a href="https://clojuredocs.org/clojure.core/if-let"><code>if-let</code></a> macros when I learned Clojure a few years ago.
|
||
|
They're used like <code>let</code> to bind values to variables, but if a value is <code>nil</code>
|
||
|
then <code>when-let</code> will return <code>nil</code> without running the body, and <code>if-let</code> will
|
||
|
execute its alternative branch.</p>
|
||
|
|
||
|
<p>Let's implement them in Common Lisp. Once we're done writing them we'll use
|
||
|
them like this:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">when-let <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">name 'steve</span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">format t <span class="string">"Hello, ~A"</span> name</span>)</span></span>)</span>
|
||
|
|
||
|
<span class="comment">; => Hello, STEVE
|
||
|
</span>
|
||
|
<span class="paren1">(<span class="code">when-let <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">name nil</span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">format t <span class="string">"Hello, ~A"</span> name</span>)</span></span>)</span>
|
||
|
|
||
|
<span class="comment">; => Nothing printed, nil returned
|
||
|
</span>
|
||
|
<span class="paren1">(<span class="code">if-let <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">name nil</span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">format t <span class="string">"Hello, ~A"</span> name</span>)</span>
|
||
|
<span class="paren2">(<span class="code">format t <span class="string">"Hello, unnamed person!"</span></span>)</span></span>)</span>
|
||
|
|
||
|
<span class="comment">; => Hello, unnamed person!</span></span></code></pre>
|
||
|
|
||
|
<h2 id="s2-a-first-attempt"><a href="index.html#s2-a-first-attempt">A First Attempt</a></h2>
|
||
|
|
||
|
<p>A simple first attempt at writing <code>when-let</code> might look something like this:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> when-let <span class="paren2">(<span class="code">binding &body body</span>)</span>
|
||
|
<span class="string">"Bind `binding` and execute `body`, short-circuiting on `nil`.
|
||
|
|
||
|
This macro combines `when` and `let`. It takes a binding and binds
|
||
|
it like `let` before executing `body`, but if the binding's value
|
||
|
evaluates to `nil`, then `nil` is returned.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(when-let ((a 1))
|
||
|
(list a))
|
||
|
; =>
|
||
|
(1)
|
||
|
|
||
|
(when-let ((a nil))
|
||
|
(list a))
|
||
|
; =>
|
||
|
NIL
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code">destructuring-bind <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">symbol value</span>)</span></span>)</span> binding
|
||
|
`<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">,symbol ,value</span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">when ,symbol
|
||
|
,@body</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>A first attempt at <code>if-let</code> looks similar:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> if-let <span class="paren2">(<span class="code">binding then else</span>)</span>
|
||
|
<span class="string">"Bind `binding` and execute `then` if true, or `else` otherwise.
|
||
|
|
||
|
This macro combines `if` and `let`. It takes a binding and binds
|
||
|
it like `let` before executing `then`, but if the binding's value
|
||
|
evaluates to `nil` the `else` branch is executed (with no binding
|
||
|
in effect).
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(if-let ((a 1))
|
||
|
(list a)
|
||
|
'nope)
|
||
|
; =>
|
||
|
(1)
|
||
|
|
||
|
(if-let ((a nil))
|
||
|
(list a)
|
||
|
'nope)
|
||
|
; =>
|
||
|
NOPE
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code">destructuring-bind <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">symbol value</span>)</span></span>)</span> binding
|
||
|
`<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">,symbol ,value</span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">if</span></i> ,symbol
|
||
|
,then
|
||
|
,else</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is a decent attempt at a first pass, but already there are a couple of
|
||
|
things to note.</p>
|
||
|
|
||
|
<p>First: we have documentation, with examples! The docstrings are longer than the
|
||
|
macros themselves, and <em>this is fine</em>! I always prefer to err on the side of
|
||
|
being more clear than saving space. If you're a little more verbose than
|
||
|
necessary some experts might have to flick their scroll wheels, but if you're
|
||
|
too terse you can leave someone wallowing in confusion. Experts should know how
|
||
|
to skim documentation quickly if they're really experts, so help out the newer
|
||
|
people and be clear!</p>
|
||
|
|
||
|
<p>I didn't make the <code>else</code> branch optional like it is in Common Lisp's <code>if</code>
|
||
|
because one-armed <code>if</code>s are a stylistic abomination.</p>
|
||
|
|
||
|
<p>We could have added some <code>check-type</code> statements to make sure the <code>symbol</code> is
|
||
|
<em>actually</em> a symbol, but since it's getting compiled to a <code>let</code> the error will
|
||
|
be caught there immediately anyway.</p>
|
||
|
|
||
|
<h2 id="s3-multiple-bindings"><a href="index.html#s3-multiple-bindings">Multiple Bindings</a></h2>
|
||
|
|
||
|
<p>Our first attempt works, but only supports a single binding. Clojure's versions
|
||
|
of these macros quit here, but we can do better. Let's make our macros support
|
||
|
multiple bindings. First we'll upgrade <code>when-let</code>:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> when-let <span class="paren2">(<span class="code">bindings &body body</span>)</span>
|
||
|
<span class="string">"Bind `bindings` and execute `body`, short-circuiting on `nil`.
|
||
|
|
||
|
This macro combines `when` and `let`. It takes a list of bindings
|
||
|
and binds them like `let` before executing `body`, but if any
|
||
|
binding's value evaluates to `nil`, then `nil` is returned.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(when-let ((a 1)
|
||
|
(b 2))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
(1 2)
|
||
|
|
||
|
(when-let ((a nil)
|
||
|
(b 2))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
NIL
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">symbols <span class="paren5">(<span class="code">mapcar #'first bindings</span>)</span></span>)</span></span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> ,bindings
|
||
|
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">and ,@symbols</span>)</span>
|
||
|
,@body</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>And now <code>if-let</code>:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> if-let <span class="paren2">(<span class="code">bindings then else</span>)</span>
|
||
|
<span class="string">"Bind `bindings` and execute `then` if all are true, or `else` otherwise.
|
||
|
|
||
|
This macro combines `if` and `let`. It takes a list of bindings and
|
||
|
binds them like `let` before executing `then`, but if any binding's
|
||
|
value evaluates to `nil` the `else` branch is executed (with no
|
||
|
bindings in effect).
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(if-let ((a 1)
|
||
|
(b 2))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
(1 2)
|
||
|
|
||
|
(if-let ((a nil)
|
||
|
(b 2))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
NOPE
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">symbols <span class="paren5">(<span class="code">mapcar #'first bindings</span>)</span></span>)</span></span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> ,bindings
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren5">(<span class="code">and ,@symbols</span>)</span>
|
||
|
,then
|
||
|
,else</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Note how we've updated the docstrings to be clear about the new behavior: if
|
||
|
<em>any</em> binding is <code>nil</code>, the alternate case takes over.</p>
|
||
|
|
||
|
<p>We've also updated the parameter names to be <code>bindings</code> (plural). One thing
|
||
|
I love about Common Lisp is that it's a Lisp 2, so you can almost always use
|
||
|
nice names for function parameters instead of mangling them to avoid shadowing
|
||
|
functions (e.g. <code>(defun filter (function list) ...)</code> instead of <code>(defun filter
|
||
|
(fn lst) ...)</code>). Take advantage of this and give your parameters descriptive,
|
||
|
pronounceable names. You'll thank yourself every time your editor shows you the
|
||
|
arglist.</p>
|
||
|
|
||
|
<p>If you've read other blog posts about implementing these macros, this is where
|
||
|
they probably stopped. But let's keep going, there's still much more to dig
|
||
|
into!</p>
|
||
|
|
||
|
<h2 id="s4-adding-some-stars"><a href="index.html#s4-adding-some-stars">Adding Some Stars</a></h2>
|
||
|
|
||
|
<p>Now that we've got <code>if-let</code> and <code>when-let</code>, the obvious next step is to add
|
||
|
<code>if-let*</code> and <code>when-let*</code>. We could do this by changing the <code>let</code> each macro
|
||
|
emits to a <code>let*</code>, but before we rush ahead let's think about how people will
|
||
|
use these macros to see if that change would make sense.</p>
|
||
|
|
||
|
<p>The point of using a <code>let*</code> instead of a <code>let</code> is so that later variables can
|
||
|
refer back to earlier ones:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">name <span class="paren4">(<span class="code">read-string</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">length <span class="paren4">(<span class="code">length name</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="comment">; ...
|
||
|
</span> </span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The point of using our <code>when-</code> and <code>if-</code> variants is to short-circuit and escape
|
||
|
on <code>nil</code>. With the way our macros are currently written, <em>all</em> the variables
|
||
|
get bound before they <em>all</em> get checked for <code>nil</code> in the <code>and</code>. This works for
|
||
|
the <code>-let</code> variants but isn't ideal for the new <code>-let*</code> variants. If we're
|
||
|
using <code>when-let*</code> it would be nice if the later variables could assume the
|
||
|
earlier ones are non-<code>nil</code>.</p>
|
||
|
|
||
|
<p>This means we'll want to bail out <em>immediately</em> after the first <code>nil</code> value is
|
||
|
detected. This is a little bit trickier than what we've currently got. There
|
||
|
are a number of ways we could do it, but I'll save us from hitting a dead-end
|
||
|
rabbit hole later and implement <code>when-let*</code> like this:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> when-let* <span class="paren2">(<span class="code">bindings &body body</span>)</span>
|
||
|
<span class="string">"Bind `bindings` serially and execute `body`, short-circuiting on `nil`.
|
||
|
|
||
|
This macro combines `when` and `let*`. It takes a list of bindings
|
||
|
and binds them like `let*` before executing `body`, but if any
|
||
|
binding's value evaluates to `nil` the process stops and `nil` is
|
||
|
immediately returned.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(when-let* ((a (progn (print :a) 1))
|
||
|
(b (progn (print :b) (1+ a)))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
:A
|
||
|
:B
|
||
|
(1 2)
|
||
|
|
||
|
(when-let* ((a (progn (print :a) nil))
|
||
|
(b (progn (print :b) (1+ a))))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
:A
|
||
|
NIL
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code"><i><span class="symbol">block</span></i></span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">block</span></i> ,<i><span class="symbol">block</span></i>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">let*</span></i> ,<span class="paren5">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren6">(<span class="code">symbol value</span>)</span> <span class="keyword">:in</span> bindings
|
||
|
<span class="keyword">:collect</span> `<span class="paren6">(<span class="code">,symbol <span class="paren1">(<span class="code">or ,value
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">return-from</span></i> ,<i><span class="symbol">block</span></i> nil</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
,@body</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>There are a few things we can talk about here before we move on to <code>if-let*</code>.</p>
|
||
|
|
||
|
<p>First: we've documented the macro. The examples are a little more verbose than
|
||
|
the previous ones, but the added side effects explicitly show the
|
||
|
short-circuiting evaluation.</p>
|
||
|
|
||
|
<p>This implementation is much less trivial than the ones we've got so far, so
|
||
|
let's look at a macroexpansion to see what's happening:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">macroexpand-1
|
||
|
'<span class="paren2">(<span class="code">when-let* <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">a nil</span>)</span>
|
||
|
<span class="paren4">(<span class="code">b <span class="paren5">(<span class="code">1+ a</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">list a b</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="comment">; =>
|
||
|
</span><span class="paren1">(<span class="code"><i><span class="symbol">BLOCK</span></i> <span class="keyword">#:BLOCK563</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">LET*</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">A <span class="paren5">(<span class="code">OR NIL <span class="paren6">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:BLOCK563</span> NIL</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">B <span class="paren5">(<span class="code">OR <span class="paren6">(<span class="code">1+ A</span>)</span> <span class="paren6">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:BLOCK563</span> NIL</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">LIST A B</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The <code>when-let*</code> expands into a <code>block</code> wrapped around a <code>let*</code>. As we're
|
||
|
binding each variable it's checking for <code>nil</code> with <code>or</code>. If it ever sees <code>nil</code>
|
||
|
it will return from the block immediately to escape. If it never sees <code>nil</code> it
|
||
|
will eventually reach the body and return normally.</p>
|
||
|
|
||
|
<p>We could have used a series of nested <code>let</code>s and <code>if</code>s here, and it would have
|
||
|
been easier to read. But the fact that all the variables are bound in a single
|
||
|
<code>let*</code> will be important later, so you're just going to have to trust me for
|
||
|
now.</p>
|
||
|
|
||
|
<p>We've named the block with a gensym to avoid clobbering any <code>nil</code> block the user
|
||
|
might already have set up. I explicitly specified the <code>nil</code> return value in the
|
||
|
<code>return-from</code>, but this isn't required because it's optional.</p>
|
||
|
|
||
|
<p><code>if-let*</code> is a more difficult, because we need to make sure the appropriate
|
||
|
branch gets evaluated:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> if-let* <span class="paren2">(<span class="code">bindings then else</span>)</span>
|
||
|
<span class="string">"Bind `bindings` serially and execute `then` if all are true, or `else` otherwise.
|
||
|
|
||
|
This macro combines `if` and `let*`. It takes a list of bindings and
|
||
|
binds them like `let*` before executing `then`, but if any binding's
|
||
|
value evaluates to `nil` the process stops and the `else` branch is
|
||
|
immediately executed (with no bindings in effect).
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(if-let* ((a (progn (print :a) 1))
|
||
|
(b (progn (print :b) (1+ a)))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
:B
|
||
|
(1 2)
|
||
|
|
||
|
(if-let* ((a (progn (print :a) nil))
|
||
|
(b (progn (print :b) (1+ a))))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
NOPE
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code">outer inner</span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">block</span></i> ,outer
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">block</span></i> ,inner
|
||
|
<span class="paren5">(<span class="code"><i><span class="symbol">let*</span></i> ,<span class="paren6">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren1">(<span class="code">symbol value</span>)</span> <span class="keyword">:in</span> bindings
|
||
|
<span class="keyword">:collect</span> `<span class="paren1">(<span class="code">,symbol <span class="paren2">(<span class="code">or ,value
|
||
|
<span class="paren3">(<span class="code"><i><span class="symbol">return-from</span></i> ,inner nil</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren6">(<span class="code"><i><span class="symbol">return-from</span></i> ,outer ,then</span>)</span></span>)</span></span>)</span>
|
||
|
,else</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is a little hairy, so let's break down what's happening. An <code>if-let*</code> will
|
||
|
macroexpand into something like:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">block</span></i> outer
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">block</span></i> inner
|
||
|
<span class="paren3">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren4">(<span class="code">...bindings...</span>)</span>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">return-from</span></i> outer then</span>)</span></span>)</span></span>)</span>
|
||
|
else</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>We set up a pair of blocks and begin binding the variables. If all the bindings
|
||
|
succeed we return the <code>then</code> branch from the outermost block (and yes, before
|
||
|
you go check: <code>return-from</code> works fine with multiple values).</p>
|
||
|
|
||
|
<p>If any of the bindings fail we return from the inner block immediately. This
|
||
|
skips all the remaining bindings plus the <code>then</code> and continues along to the
|
||
|
<code>else</code>, which executes and returns normally.</p>
|
||
|
|
||
|
<p>A full macroexpansion ends up looking like this:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">macroexpand-1
|
||
|
'<span class="paren2">(<span class="code">if-let* <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">a nil</span>)</span>
|
||
|
<span class="paren4">(<span class="code">b <span class="paren5">(<span class="code">1+ a</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">list a b</span>)</span>
|
||
|
'nope</span>)</span></span>)</span>
|
||
|
<span class="comment">; =>
|
||
|
</span><span class="paren1">(<span class="code"><i><span class="symbol">BLOCK</span></i> <span class="keyword">#:OUTER568</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">BLOCK</span></i> <span class="keyword">#:INNER569</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">A <span class="paren6">(<span class="code">OR NIL <span class="paren1">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:INNER569</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren5">(<span class="code">B <span class="paren6">(<span class="code">OR <span class="paren1">(<span class="code">1+ A</span>)</span> <span class="paren1">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:INNER569</span></span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:OUTER568</span> <span class="paren5">(<span class="code">LIST A B</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
'NOPE</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>It wouldn't be ideal to implement <code>if-let*</code> as a nested series of <code>let</code>s and
|
||
|
<code>if</code>s, because you'd need to duplicate the <code>else</code> code at each level. The
|
||
|
nested pair of blocks might be a little harder to understand at first, but they
|
||
|
only include the <code>else</code> in a single place (and will be important for another
|
||
|
reason soon).</p>
|
||
|
|
||
|
<h2 id="s5-consistency"><a href="index.html#s5-consistency">Consistency</a></h2>
|
||
|
|
||
|
<p>Now that we've got <code>when-let*</code> and <code>if-let*</code> short-circuiting on each binding,
|
||
|
it probably makes sense to change <code>when-let</code> and <code>if-let</code> to behave the same
|
||
|
way, instead of checking after all the variables are bound. Although the later
|
||
|
variables don't rely on the earlier ones for these variants, it would be good if
|
||
|
the behavior were consistent.</p>
|
||
|
|
||
|
<p>To do this we can take our <code>-let*</code> versions and change the <code>let*</code> inside to
|
||
|
a <code>let</code>, update the documentation, and that's it:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> when-let <span class="paren2">(<span class="code">bindings &body body</span>)</span>
|
||
|
<span class="string">"Bind `bindings` in parallel and execute `body`, short-circuiting on `nil`.
|
||
|
|
||
|
This macro combines `when` and `let`. It takes a list of bindings and
|
||
|
binds them like `let` before executing `body`, but if any binding's value
|
||
|
evaluates to `nil` the process stops and `nil` is immediately returned.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(when-let ((a (progn (print :a) 1))
|
||
|
(b (progn (print :b) 2))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
:A
|
||
|
:B
|
||
|
(1 2)
|
||
|
|
||
|
(when-let ((a (progn (print :a) nil))
|
||
|
(b (progn (print :b) 2)))
|
||
|
(list a b))
|
||
|
; =>
|
||
|
:A
|
||
|
NIL
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code"><i><span class="symbol">block</span></i></span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">block</span></i> ,<i><span class="symbol">block</span></i>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> ,<span class="paren5">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren6">(<span class="code">symbol value</span>)</span> <span class="keyword">:in</span> bindings
|
||
|
<span class="keyword">:collect</span> `<span class="paren6">(<span class="code">,symbol <span class="paren1">(<span class="code">or ,value
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">return-from</span></i> ,<i><span class="symbol">block</span></i> nil</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
,@body</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
|
||
|
<span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> if-let <span class="paren2">(<span class="code">bindings then else</span>)</span>
|
||
|
<span class="string">"Bind `bindings` in parallel and execute `then` if all are true, or `else` otherwise.
|
||
|
|
||
|
This macro combines `if` and `let`. It takes a list of bindings and
|
||
|
binds them like `let` before executing `then`, but if any binding's value
|
||
|
evaluates to `nil` the process stops and the `else` branch is immediately
|
||
|
executed (with no bindings in effect).
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(if-let ((a (progn (print :a) 1))
|
||
|
(b (progn (print :b) 2))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
:B
|
||
|
(1 2)
|
||
|
|
||
|
(if-let ((a (progn (print :a) nil))
|
||
|
(b (progn (print :b) 2)))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
NOPE
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code">outer inner</span>)</span>
|
||
|
`<span class="paren3">(<span class="code"><i><span class="symbol">block</span></i> ,outer
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">block</span></i> ,inner
|
||
|
<span class="paren5">(<span class="code"><i><span class="symbol">let</span></i> ,<span class="paren6">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren1">(<span class="code">symbol value</span>)</span> <span class="keyword">:in</span> bindings
|
||
|
<span class="keyword">:collect</span> `<span class="paren1">(<span class="code">,symbol <span class="paren2">(<span class="code">or ,value
|
||
|
<span class="paren3">(<span class="code"><i><span class="symbol">return-from</span></i> ,inner nil</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren6">(<span class="code"><i><span class="symbol">return-from</span></i> ,outer ,then</span>)</span></span>)</span></span>)</span>
|
||
|
,else</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<h2 id="s6-declarations"><a href="index.html#s6-declarations">Declarations</a></h2>
|
||
|
|
||
|
<p>Before we finish, we should make sure we've done things <em>right</em>. Something
|
||
|
that's often forgotten when making new control structures with macros is
|
||
|
handling declarations properly. When writing a normal <code>let</code>, you can put
|
||
|
declarations immediately inside the body, like this:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">foo <span class="paren4">(<span class="code">some-function</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">bar <span class="paren4">(<span class="code">some-other-function</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">optimize safety</span>)</span>
|
||
|
<span class="paren3">(<span class="code">type integer foo</span>)</span>
|
||
|
<span class="paren3">(<span class="code">type string bar</span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">do-something foo bar</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>If we think about how our <code>when-let</code> (and the <code>-let*</code> version) macroexpands
|
||
|
we'll see that we don't need to do anything — it will work fine the way
|
||
|
we've written it:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">macroexpand-1
|
||
|
'<span class="paren2">(<span class="code">when-let <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">foo <span class="paren5">(<span class="code">some-function</span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">bar <span class="paren5">(<span class="code">some-other-function</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">declare <span class="paren4">(<span class="code">optimize safety</span>)</span>
|
||
|
<span class="paren4">(<span class="code">type integer foo</span>)</span>
|
||
|
<span class="paren4">(<span class="code">type string bar</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">do-something foo bar</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="comment">; =>
|
||
|
</span><span class="paren1">(<span class="code"><i><span class="symbol">BLOCK</span></i> <span class="keyword">#:BLOCK586</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">LET</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">FOO <span class="paren5">(<span class="code">OR <span class="paren6">(<span class="code">SOME-FUNCTION</span>)</span> <span class="paren6">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:BLOCK586</span> NIL</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">BAR <span class="paren5">(<span class="code">OR <span class="paren6">(<span class="code">SOME-OTHER-FUNCTION</span>)</span> <span class="paren6">(<span class="code"><i><span class="symbol">RETURN-FROM</span></i> <span class="keyword">#:BLOCK586</span> NIL</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">DECLARE <span class="paren4">(<span class="code">OPTIMIZE SAFETY</span>)</span>
|
||
|
<span class="paren4">(<span class="code">TYPE INTEGER FOO</span>)</span>
|
||
|
<span class="paren4">(<span class="code">TYPE STRING BAR</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">DO-SOMETHING FOO BAR</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is why I insisted on implementing the macros with a single <code>let</code> binding
|
||
|
all the variables. If we tried to do this with a series of nested <code>let</code>s and
|
||
|
<code>if</code>s we'd have to try to parse the declarations and put the appropriate ones
|
||
|
for each variable under the corresponding <code>let</code>, and this would be an absolute
|
||
|
nightmare (plus you wouldn't even be able to exclude <code>nil</code> from the type,
|
||
|
because the <code>if</code> wouldn't happen until after the declaration!).</p>
|
||
|
|
||
|
<p>Unfortunately <code>if-let</code> is going to be some more work. Let's think about an
|
||
|
example:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">if-let <span class="paren2">(<span class="code"><span class="paren3">(<span class="code">foo <span class="paren4">(<span class="code">some-function</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">bar <span class="paren4">(<span class="code">some-other-function</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">declare <span class="paren3">(<span class="code">optimize safety</span>)</span>
|
||
|
<span class="paren3">(<span class="code">type integer foo</span>)</span>
|
||
|
<span class="paren3">(<span class="code">type string bar</span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">do-something foo bar</span>)</span>
|
||
|
<span class="paren2">(<span class="code">do-something-else</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>We're going to want the declarations to <em>only</em> apply to the <code>then</code> branch,
|
||
|
because that's the branch that has the variables whose types we might want to
|
||
|
declare. If the user wants some declarations in the <code>else</code> branch they can
|
||
|
wrap that branch in a <code>locally</code> and add them there.</p>
|
||
|
|
||
|
<p>We're going to need a way to grab any declarations the user has given out of the
|
||
|
body of the <code>if-let</code>. Luckily Alexandria has a function called <code>parse-body</code>
|
||
|
that will do this for us.</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> if-let <span class="paren2">(<span class="code">bindings &body body</span>)</span>
|
||
|
<span class="string">"Bind `bindings` in parallel and execute `then` if all are true, or `else` otherwise.
|
||
|
|
||
|
`body` must be of the form `(...optional-declarations... then else)`.
|
||
|
|
||
|
This macro combines `if` and `let`. It takes a list of bindings and
|
||
|
binds them like `let` before executing the `then` branch of `body`, but
|
||
|
if any binding's value evaluates to `nil` the process stops there and the
|
||
|
`else` branch is immediately executed (with no bindings in effect).
|
||
|
|
||
|
If any `optional-declarations` are included they will only be in effect
|
||
|
for the `then` branch.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
(if-let ((a (progn (print :a) 1))
|
||
|
(b (progn (print :b) 2)))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
:B
|
||
|
(1 2)
|
||
|
|
||
|
(if-let ((a (progn (print :a) nil))
|
||
|
(b (progn (print :b) 2)))
|
||
|
(list a b)
|
||
|
'nope)
|
||
|
; =>
|
||
|
:A
|
||
|
NOPE
|
||
|
|
||
|
"</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">alexandria:with-gensyms</span></i> <span class="paren3">(<span class="code">outer inner</span>)</span>
|
||
|
<span class="paren3">(<span class="code">multiple-value-bind <span class="paren4">(<span class="code">body declarations</span>)</span> <span class="paren4">(<span class="code">alexandria:parse-body body</span>)</span>
|
||
|
<span class="paren4">(<span class="code">destructuring-bind <span class="paren5">(<span class="code">then else</span>)</span> body
|
||
|
`<span class="paren5">(<span class="code"><i><span class="symbol">block</span></i> ,outer
|
||
|
<span class="paren6">(<span class="code"><i><span class="symbol">block</span></i> ,inner
|
||
|
<span class="paren1">(<span class="code"><i><span class="symbol">let</span></i> ,<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="keyword">:for</span> <span class="paren3">(<span class="code">symbol value</span>)</span> <span class="keyword">:in</span> bindings
|
||
|
<span class="keyword">:collect</span> `<span class="paren3">(<span class="code">,symbol <span class="paren4">(<span class="code">or ,value
|
||
|
<span class="paren5">(<span class="code"><i><span class="symbol">return-from</span></i> ,inner nil</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
,@declarations
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">return-from</span></i> ,outer ,then</span>)</span></span>)</span></span>)</span>
|
||
|
,else</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Whew! We parse the body with <code>parse-body</code>, destructure what's left of it (to
|
||
|
make sure we have our two branches), and shove the declarations where they
|
||
|
belong.</p>
|
||
|
|
||
|
<p><code>if-let*</code> is exactly the same, but with a <code>let*</code> in the macro. I'll let you
|
||
|
write that one yourself.</p>
|
||
|
|
||
|
<h2 id="s7-result"><a href="index.html#s7-result">Result</a></h2>
|
||
|
|
||
|
<p>We've now got <code>when-let</code>, <code>when-let*</code>, <code>if-let</code>, and <code>if-let*</code> working properly.
|
||
|
They all support multiple bindings, short-circuit appropriately, handle
|
||
|
declarations correctly, and are documented clearly.</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>
|