# Where parallels cross

Interesting bits of life

# On the power of macros: a dynamic lazy let

Recently I have been working on making moldable-emacs easier to extend. One of the challenges was to define common variables in a single place. For example, I wanted to make this code

(:given (:fn (let ((a (run-something))
(b (run-something-else)))
...))
:then (:fn (let ((a (run-something))
(b (run-something-else)))
...)))


into this one

(:let ((a (run-something))
(b (run-something-else)))
:given (:fn ...)
:then (:fn ...))


The second piece of code can save a bit of copy paste for when I write molds. I also need that a and b get calculated lazily for the :given clause: if there is something like (and nil a b), I want to skip to calculate the bindings (because it may be useless).

Since I am creating a little Domain Specific Language for defining molds, your Lisp-senses should scream: macros!

Let's start easy.

If you want to write a macro that wraps thing is a let, it is simple.

(defmacro with-my-let (&rest body)
(let ((a (+ 1 2)))
,@body))

(with-my-let (+ a 1))

4


This is good and easy because we know a in advance. In my case I realize the bindings of the let only at run-time. We then need as input the list of bindings.

(defmacro with-my-let (let-bindings &rest body)
(let (,@let-bindings)
,@body))

(with-my-let ((a (+ 1 2))) (+ a 1))

4


So far so good! And what if I get let-bindings as a variable?

(defmacro with-my-let (let-bindings &rest body)
(let (,@let-bindings)
,@body))

(let ((let-bindings '((a (+ 1 2)))))
(with-my-let let-bindings (+ a 1)))


This breaks because the with-my-let macro expands to the following.

(let let-bindings
(+ a 1))


This happens because macros don't evaluate their arguments. But even if they did, let-binding obtains a value only at run time! This is the first challenge: get the bindings at run time.

We need to do this in two steps:

1. "pause" the generation of the code until run time
2. at run time inject the value in the code

Here is how it looks.

(defmacro with-my-let (let-bindings &rest body)
(funcall
(lambda (bindings body)
(eval (let* ,bindings ;; here ,bindings = ((a (+ 1 2)))
,@body)))
,let-bindings ;; here ,let-bindings = let-bindings-var
',body))

(let ((let-bindings-var '((a (+ 1 2)))))
(with-my-let let-bindings-var (+ a 1)))

4


The trick is a function that takes the let-bindings variable we pass. This function is somewhat like a macro: it produces a sexp itself (the bit (let)! But, it evaluates it as well (the eval bit).

Well, let me show you how it expands:

(let ((let-bindings-var '((a (+ 1 2)))))
(funcall
(lambda
(bindings body)
(eval
(let ,bindings ,@body)))
let-bindings-var
'((+ a 1))))


I must confess: it took me some time to fully understand what I did when I wrote it!

Now lets get in the funny bit: what if we want to have lazy bindings? By lazy I mean bindings getting a value only at the latest possible moment. This means that if we don't use a value, we don't invest any time in producing it!

Emacs is so cool that it already has a way to do that: thunk.el. This library provides thunk-let*, which does exactly what we need: it makes all bindings lazy!

So the macro will change only slightly to become amazing!

(defmacro with-my-let (let-bindings &rest body)
(funcall
(lambda (bindings body)
(eval (thunk-let* ,bindings ;; here ,bindings = ((a (+ 1 2)))
,@body)
t))
,let-bindings ;; here ,let-bindings = let-bindings-var
',body))

(let ((let-bindings-var '((a (+ 1 2))
(slow-poke (sleep-for 20)))))
(with-my-let let-bindings-var (+ a 1)))

4


If you try this code, you will see that you will skip slow-poke's long sleep time! All we needed to do was to substitute our let* with thunk-let* AND make sure that sexp is evaluated in a lexical context. You can do that by giving eval an extra argument.

How amazing is this macro?! Well if it is not, let me know because I would still like to improve it, if possible.

And keep in mind that your body must be inline! For example, this cannot work:

(defun f (x)
(+ a x))

(let ((let-bindings-var '((a (+ 1 2))
(slow-poke (sleep-for 20)))))
(with-my-let let-bindings-var (f 1)))


So if you have something like that, you have to pass the binding or inject the function code in the body`.

Now that I gave you that caveat.. we are done!

Thanks to stick around so far and hopefully you will find inspiration to write your own useful (lazy?!) macros!

Happy macro-ing!