
Fun with Macros: If-Let and When-Let - stevelosh
http://stevelosh.com/blog/2018/07/fun-with-macros-if-let/
======
brlewis
I like the macro transformers in R5RS Scheme. Here's how the multi-binding if-
let and when-let look:

    
    
      (define-syntax if-let
        (syntax-rules ()
          ((if-let ((var value) ...)
                  consequent ...)
           (let ((var value) ...)
             (if (and var ...)
                 consequent ...)))))
              
      (define-syntax when-let
        (syntax-rules ()
          ((when-let (binding)
                     body ...)
           (if-let (binding)
                   (begin body ...)))))

~~~
shawn
Arc has corrupted me to barely be able to tolerate all those parens.

    
    
      (mac iflet (var condition . body)
        `(let ,var ,condition
          (if ,var ,@body)))
    
      (mac whenlet (var condition . body)
        `(iflet ,var ,condition (do ,@body)))

~~~
christianbryant
What kind of project are you working on with Arc? Or are you just having fun
for now ;-)

------
WalterGR
What’s a example of these macros being used usefully? All I can find are
contrived examples.

Why not use `if` and `when` directly? Is creating new names for variables —
which is what `if-let` and `when-let` seems to contribute — that common of a
pattern?

I must be missing something.

~~~
girzel
They're pure convenience, but they're pretty convenient. It's very common to
have patterns like:

    
    
      (let ((result (do-some-calculation)))
        (when result
          (operate-on result)))
    

`when-let' would just skip one "layer" of code, and I believe avoid assigning
a local variable if (do-some-calculation) fails. Just a convenience.

(edit: trying to fix formatting)

~~~
girzel
Never mind, it still binds a local variable (at least in elisp, where I just
tried it).

~~~
dreamcompiler
It has to, otherwise it would have to execute the initform twice.

~~~
girzel
Yeah, that finally clicked afterwards...

------
TeMPOraL
Maybe I'm missing the point of the exercise, but since the author is already
using Alexandria in these examples, and Alexandria _provides when-let and if-
let macros as well_ , then... why not just explain - or at least compare with
and discuss - the implementation in Alexandria?

Skipping docstrings, those are exactly:

    
    
      (defmacro if-let (bindings &body (then-form &optional else-form))
          (let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))
                                   (list bindings)
                                   bindings))
               (variables (mapcar #'car binding-list)))
          `(let ,binding-list
             (if (and ,@variables)
                 ,then-form
                 ,else-form))))
      
      (defmacro when-let (bindings &body forms)
        (let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))
                                 (list bindings)
                                 bindings))
               (variables (mapcar #'car binding-list)))
          `(let ,binding-list
             (when (and ,@variables)
               ,@forms))))

~~~
stevelosh
Alexandria's versions are basically the same as the "multiple bindings"
versions, about halfway through the post[1]. So they are
explained/compared/discussed... I just didn't mention that those versions
happen to be in Alexandria. I suppose I could add a note, though calling out a
particular library for having a less than ideal implementation seems a little
rude.

[1]: They do allow a single binding as a special case, true.

~~~
TeMPOraL
> _calling out a particular library for having a less than ideal
> implementation seems a little rude_

Why? As long as you call it "less than ideal", and not say "it sucks" :).
Maybe in the end a patch will find its way upstream; I hear that someone is
contributing to this library, sometimes.

------
nickdrozd
You could save a little work by noting that _when_ is a special case of _if_
and then writing _when-let_ as a special case of _if-let_. Something like:

    
    
      (defmacro when-let (bindings &rest body)
        `(if-let ,bindings
             (progn ,body)))
    

That's how it's implemented in Emacs at least. Maybe there are some edge cases
involving declarations or whatever where this wouldn't work.

~~~
stevelosh
You'd need to parse out the declarations, yeah. Otherwise they'd get shoved
inside the progn which wouldn't work.

~~~
junke
You could also introduce a "locally" form (for those wondering:
[http://www.lispworks.com/documentation/lw61/CLHS/Body/s_loca...](http://www.lispworks.com/documentation/lw61/CLHS/Body/s_locall.htm))

------
cryptonector
Paul Graham called these "anaphoric" macros in On LISP.

~~~
stevelosh
The "anaphora" in "anaphoric" typically refers to how it binds the variable
"it" automatically, without having to specify the variable name. Since if-let
and when-let require you to explicitly specify the variable name, I don't
think most people would consider them anaphoric macros.
[https://en.wikipedia.org/wiki/Anaphoric_macro](https://en.wikipedia.org/wiki/Anaphoric_macro)

If you want actual anaphoric macros in CL, here they are: [https://www.common-
lisp.net/project/anaphora/](https://www.common-lisp.net/project/anaphora/)

------
kazinator
iflet and whenlet are written in C in TXR Lisp.

See the me_iflet_whenlet function in eval.c:

[http://www.kylheku.com/cgit/txr/tree/eval.c#n4125](http://www.kylheku.com/cgit/txr/tree/eval.c#n4125)

