Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to format lisp code?

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:

  • The declaration of factorial doesn't have leading spaces. I think that's normal.
  • The body of the function requires two leading spaces each line.
  • we add four spaces at the first clause and the beginning of the second clause of the if statement. In total 6 spaces.
  • We add 11 more spaces in the last two lines, the rest of the second clause of the if statement. In total 17 spaces.

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?

like image 533
lightning_missile Avatar asked Dec 03 '22 15:12

lightning_missile


1 Answers

tl;dr: it makes the nesting of expressions clear


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.

like image 151
Alexis King Avatar answered Dec 20 '22 00:12

Alexis King