Hung-Yi’s Journal

Front-end Developer, Emacs Adventurer, Home Cook

03 Mar 2021

Convolute Lisp S-Expressions With Smartparens

"Convoluting a lisp s-expression" sounds like computer science ivory tower bollocks, but after I actually learned what it was and how to use it, I'm seeing it pop up quite regularly when writing lisp. Let me show you the magic; I promise every time you get to use it, you'll feel like a king!

Let's say you've been writing some code in a stream-of-consciousness way. First you bind some local variables using let and call do-something:

(let ((x 1)
      (y 2))
  (do-something x y))

Oh, but now you only want to do-something when condition-p is true:

(let ((x 1)
      (y 2))
  (when condition-p
    (do-something x y)))

OK, sweet!

…but wait, if condition-p is false then the let bindings are useless! You can see that the when should have wrapped around the let instead of just the body inside let. Now you have to tediously flip everything around by hand, right?

No! sp-convolute-sexp to the rescue 🦸‍♂️

Put your point at the opening parenthesis of (do-something x y) and run M-x sp-convolute-sexp and in one fell swoop…

(when condition-p
  (let ((x 1)
        (y 2))
    (do-something x y)))

So many keystrokes saved, right? Never mind feeling like a king — you'll feel like a wizard 🧙

A More Abstract Explanation

It doesn't matter how many s-expressions there are in the surrounding code — they usually won't get in the way. You just have to keep a few specific rules in mind. Let's break it down in an abstract example, with no distracting "reality" or whatever that is 🤓

(outer x y z
       (inner
        a b c d e f)
       j k l)

If you convolute around any of the s-expressions a b c d e f then the s-expression surrounding outer will be swapped with the s-expression surrounding inner and all the s-expressions immediately preceding your cursor will be chopped off and moved alongside inner.

So when we simply convolute around a we'll get:

(inner
 (outer x y z
        a b c d e f
        j k l))

But had we convoluted around d then we would have gotten:

(inner
 a b c (outer x y z
              d e f
              j k l))

Keybinding Tips

I like to map it to a binding that includes the % character, since it visually reminds me of swapping something above to below and reinforces that pattern in my head so I remember to use it.

I've included it in my Smartparens hydra, which I've been working on improving as I write more and more lisp. But that's a future post.