All Lisp Indentation Schemes Are Ugly
By Artyom BologovOnce you get used to Lisp, you stop noticing the parentheses and rely on indentation instead. That's partially why there are several alternative syntaxes based solely on indentation. Like Wisp, or sweet expressions. But then, the question stands: how to indent the code, actually? Especially so—in Lispy syntax.
No Indent: One Can Only Go So Far
A solution that will likely satisfy a proponent of any indentation style: "Just put it all on one line lol." Of course, lines are not infinitely readable and there's a column cap, (whether natural or enforced.) So this line (adapted from cl-blc,) while devoid of indentation problems, is unreadable:
That's the problem statement: some forms need multiple lines and indentation. But what kind of indentation?
(Dis)Functional Aligned Indent
There's an established style of indentation: align the function arguments on the same column:
This style if useful in reflecting the code structure: just look at what's indented and what's outdented.
It works especially well for short function/macro/form names, like +
or vec:get
.
Not so well for long ones:
Nineteen! Nineteen spaces of indentation! It's getting unruly. Such an indent, when used in deeply nested code, makes it too wide and unreadable. If you add the strict one-per-line alignment of arguments, it's also painfully long line-wise. Let's handle the verticality first:
Space-filling Indent
No sane Lisper would write a loop
with every keyword on its own line:
(Some pretty-printers do that too (I'm looking at you, ECL!), but that's a topic for another day.)
We don't have to put every argument on its own line. That's the intuition behind the space-filling indent:
One can go as far as splitting the argument list in arbitrary places. Regardless of semantics. (Looking at you, SBCL!) Putting some keyword arguments on the first line, and then some on the second/third/etc. This utilizes the space efficiently enough to be used. But what if one's stuck really deep in nesting levels?
Sick Macro Indent
Now what I'm about to suggest is likely not to your taste:
This style of indentation (putting function name on one line, and arguments on the other) was frowned upon more than once in my practice:
- It messes up with nesting identification: one space is not enough.
- It makes the code too vertical.
- And it certainly isn't idiomatic.
But what this style achieves is perfect indentation control. You only get one space of indentation per form. Complex algorithms are easier to read when written in this style.
And! This style also plays well with most indentation tools, even the simplest ones. I had this situation more than once:
- Writing a
with-*
macro in Scheme; - Using it;
- And realizing that Emacs/Geiser indentation functions think that this macro is a procedure.
Indenting all the arguments of the progn
/begin
-like macro in this sick style helps:
Another Solution: Threading Macros
I worked on a deeply nested corporate codebase functions. In Clojure. And I realized why threading macros exist. To make the logic more sequential and readable, true. But also to tame excessive nesting!
Unfortunately, threading/arrow macros don't always work. Common Lisp in particular is extremely unfriendly to threading macros. Arrows imply a consistent thread-first or thread-last functions. But CL's standard lib is too inconsistent for that to work. So we're left with picking an indentation style we don't necessarily like.
What Is Your Favorite Style?
I often prefer the macro-like indentation, because I sometimes write deeply nested code. But I see the value in all the other indentation schemes!