Consider this iterative factorial procedure from SICP.
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
Here, we see:
Why is it like this? The spacing is confusing to me. Why can't it be like java (add four spaces in every inner part of code)? How should I format lisp code?
One of the defining features of Lisps, including Scheme, is that the code you write is essentially the AST (abstract syntax tree). Languages like Java have lots of syntax, so they require parsers to properly disambiguate potentially ambiguous grammars. Infix operators are a classic example of this. Consider the following statement in Java:
int x = 1 + y * 2;
The textual representation of the code certainly doesn’t imply any tree-like structure, but in actuality, that statement has a single canonical parse that is in fact a tree. It would look something like this:
=
/ \
int x +
/ \
1 *
/ \
y 2
The equivalent Scheme code, on the other hand, makes all that nesting incredibly explicit:
(define x (+ 1 (* y 2)))
Notice how the explicit grouping creates a very well-defined tree of expressions. There is no need for operator precedence rules like there are in most other languages. This simplicity is an intentional design choice because when the source code representation is so simple, it is extremely easy to write macros that transform it. Lisps tend to make heavy use of macros because manipulating the AST is relatively painless compared to other programming languages due to how simple the syntax is.
With all this in mind, the indentation rules may become more obvious: Lisp code is usually indented in such a way so that the structure of the AST is immediately visible. Consider an alternative version of your fact-iter
example function that used a “simpler” indentation style:
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
In this particular case, the indentation is not disastrous, but the recursive call to fact-iter
is now much more difficult to visually parse. The uniformity of Lisp/Scheme syntax makes it difficult to immediately pick up on the fact that fact-iter
is being called with three arguments because the first argument is no longer aligned with the last two.
This could be at least somewhat addressed by just putting all arguments on separate lines:
(fact-iter
(* counter product)
(+ counter 1)
max-count)
This works, and is actually acceptable Lisp style. It is often a significant waste of vertical space, though, and it still makes the AST a bit harder to immediately grok because the indentation is considerably less visually striking.
For an example where using the “simpler” indentation model is disastrous in Scheme, consider the following two equivalent expressions:
(string->number (if (string? x) x
(format "~a" x)))
(string->number (if (string? x) x
(format "~a" x)))
The first example maintains the AST. It is very easy to see that the format
call is the “else” case for the if
form because it is nested under that. The second example does not maintain the AST, and it is unclear at first glance if the call to format
is nested inside the if
or if it is simply a second argument passed to string->number
. You can see that the syntax of Lisp does not really make this clear.
Scheme indentation can seem a little funky at first, but once you get used to it, it makes the code a lot easier to see without having to juggle parentheses in your head. The uniformity of the syntax is both a blessing and a curse: it makes writing macros trivial but it removes some of the visual markers that make code easier to understand. Having a more semantic indentation system helps to mitigate that drawback.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With