# ๐Ÿ“Ž All Lisp Indentation Schemes Are Ugly By Artyom Bologov (Pinky beige thumbnail with 'UGLY LISP INDENT' in huge letters. Every word is indented with a return char 'โฎ‘'. In the right top corner, 'Artyom aartaka.me Bologov' is written in likewise indented way.) Once 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 (#no-indent) 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: =================================== lisp =================================== (list (tree-transform-if predicate transformer (first tree) depth) (tree-transform-if predicate transformer (second tree) depth)) ==================== Absurdly long line of a Lisp code ==================== That's the problem statement: some forms need multiple lines and indentation. But what kind of indentation? ## (Dis)Functional Aligned Indent (#function-indent) There's an established style of indentation: align the function arguments on the same column: =================================== lisp =================================== (list (tree-transform-if predicate transformer (first tree) depth) (tree-transform-if predicate transformer (second tree) depth)) (inc! d (/ (* (mtx:get x i k) (mtx:get x j k)) (1+ (* (vec:get dl l) (vec:get eval k))))) (let ((s (if (< j i) j i)) (l (if (< j i) i j))) (+ (* s 1/2 (- (* 2 d-size) (1+ s))) l (- s))) ==================== Examples of function-like indentation ==================== 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: =================================== lisp =================================== (tree-transform-if predicate transformer (second tree) depth) ==================== A problematic function indentation ==================== 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 (#filling-indent) No sane Lisper would write a `loop` with every keyword on its own line: =================================== lisp =================================== (loop for i below 10 collect i) ==================== Ugly loop ==================== (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: =================================== lisp =================================== (loop for i below 10 collect i) ==================== Less ugly loop ==================== 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 (#macro-indent) Now what I'm about to suggest is likely not to your taste: =================================== lisp =================================== (tree-transform-if predicate transformer (second tree) depth) ==================== My indentation style suited for deeply nested code ==================== 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: =================================== scheme =================================== ;; From (mtx:with-column (uab-col uab index-ab) (mtx:set! ppab 0 index-ab (blas:dot hi-hi-eval uab-col))) ;; To (mtx:with-column (uab-col uab index-ab) (mtx:set! ppab 0 index-ab (blas:dot hi-hi-eval uab-col))) ==================== Sick indent helps you to manage macros ==================== ## Another Solution: Threading Macros (#threading) 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? (#favorite) 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! Copyright 2022-2025 Artyom Bologov (aartaka). Any and all opinions listed here are my own and not representative of my employers; future, past and present.