Hacker News new | past | comments | ask | show | jobs | submit

All Lisp indentation schemes are ugly

https://aartaka.me/lisp-indent.html
This post misses the IMO best indentation scheme for lisp, which I used for my college class where we had to use MIT scheme:

    (define (match:element variable restrictions)
        (define (ok? datum)
            (every 
                (lambda (restriction)
                    (restriction datum)
                )
                restrictions
            )
        )
        (define (element-match data dictionary succeed)
            (and 
                (ok? data)
                (let ((vcell (match:lookup variable dictionary)))
                    (if vcell
                        (and 
                            (equal? (match:value vcell) data)
                            (succeed dictionary)
                        )
                        (succeed (match:bind variable data dictionary))
                    )
                )
            )
        )
        element-match
    )
It may not be densest or most compact indentation scheme, but damn is it readable for someone without a lisp/scheme background!
> This post misses the IMO best indentation scheme for lisp [...]

Likewise, a lispier(?) and more compact compromise?:

    (define (match:element variable restrictions)
        (define (ok? datum)
            (every 
                (lambda (restriction)
                    (restriction datum))
                restrictions))
        (define (element-match data dictionary succeed)
            (and 
                (ok? data)
                (let ((vcell (match:lookup variable dictionary)))
                    (if vcell
                        (and 
                            (equal? (match:value vcell) data)
                            (succeed dictionary))
                        (succeed (match:bind variable data dictionary))))))
        element-match)
loading story #42765641
For Lisp users this is 35% extra vertical space for zero readability benefit. Or negative readability rather. When I see this formatting style I have to fix it before I can read it. It's like trying to read C code with an extra blank line after every single closing brace.
loading story #42765655
loading story #42766662
loading story #42771768
loading story #42769341
As a hobbyist that keeps trying to break into lisp, I think the absence of an obviously good formatter for lisps has made things much harder.

This looks like how I've naively tried to format things, and having started with python kind of makes sense.

But trying to read / edit code with 5 parentheses bunched together, I have the hardest time telling if they are matched or where. Thank goodness for vim / helix auto-matching.

loading story #42764717
loading story #42764728
loading story #42769518
Admit you posted this only to trigger lispers.
loading story #42771776
That is blasphemy.
You may not like it, but this is what peak lisp indentation looks like :)

Honestly though, this style for ending parenthesis of block-type nodes makes a lot of sense to me. And you still retain the benefit of macro-ing your own syntax when you need it.

The uncuddled closing parens are a sure sign of a sick mind; probably corrupted by C or the like.
They make line-by-line diffs nicer. And it's quite nice to be able to swap/move lines without messing up the closing paren, especially when not using a structural editor.
To be fair in C they don't put semicolon on its own line either.
loading story #42768344
{"deleted":true,"id":42769578,"parent":42764499,"time":1737386545,"type":"comment"}
One thing I dislike about the compact style is how adding/removing a let block causes a diff line that is just adding/removing a parent.
Do you really think it's just a matter of style whether or not you must add or remove a parenthesis? Or whether that change shows up in a diff?
A blasphemy is a very useful thing in cases when a discipline turns into a religion. (Yes, I know about jokes.)
Nicely indented easy to read blasphemy is the most dangerous kind!
Exactly what I was thinking.
I’ve been in too many arguments about Lisp indentation but that actually comes close to decent with a couple of changes: move the singleton closing parens up, and reduce to, say, 2 spaces.
More of an allman Lisp guy myself

  (
  define (match:element variable restrictions)
    (
    define (ok? datum)
      (
      every 
        (
        lambda (restriction)
          (restriction datum)
        )
        restrictions
      )
    )
  (
    define (element-match data dictionary succeed)
      (
      and 
        (ok? data)
        (
        let ((vcell (match:lookup variable dictionary)))
          (
          if vcell
            (
            and 
             (equal? (match:value vcell) data)
             (succeed dictionary)
            )
            (succeed (match:bind variable data dictionary))
          )
        )
      )
    )
    element-match
  )
I don't use Lisp but this is very readable to me, actually way more than the examples in the article by a lot. I actually understand this code, just because of how it's formatted. Maybe I'd just use 2 spaces instead of 4 but otherwise, looks good.

Granted I'm not the one to optimize for since I don't use Lisp, but this just seems sane and easy to automate, and it looks like other non-Lisp languages.

{"deleted":true,"id":42763762,"parent":42763552,"time":1737333779,"type":"comment"}
{"deleted":true,"id":42767190,"parent":42763552,"time":1737369098,"type":"comment"}
Those poor lonely parens!
loading story #42768397
The only thing that's missing is moving most of nested logic into `let` assignments.
I am sure there are some Emacs modes or plugins for other editors that could go back and forth between this and the more commonly-seen style.
Yeah, I'm surprised the article doesn't address this obvious case. It's pretty much my default indentation scheme for any language, and while I see a lot of lispers here complain about the wasted vertical space, it's undeniably easy to see which parentheses match and what the block structure is.

I'll happily tolerate some wasted space in that trade-off.

Once you have even a small amount of that background it's complete shit. Or else if it isn't, nobody wants to work with you. But at that point that probably suits you fine. If you want to work completely alone, yet be maximally productive, you've landed in a good language family.
Haha, good one!

(I hope?!)

This is one of the most disgusting things I read on HN.
Honestly that's the indentation format I use for every language. Doing it in Lisp just makes sense.

My "OTBS all the things" rules:

- Never horizontal align. Indentation is a simple tree, with each set of children indented 1 layer deeper, which can be 4 spaces or 1 tab.

    ThisKindOfThing(
                    foo,
                    bar,
                    baz);
is generally dumb.

- If a block is worth indenting for, the end of the block deserves its own line. Just do OTBS but also for parentheses if you've a long function call.

  ThisKindOfThing (
      foo,
      bar,
      baz
  );
Is just more consistent and easier to manage while still being legible.

- if you're ever splitting something across multiple lines there must be an indent.

  myObj
      .Foo()
      .Bar()
      .Baz();
- you're allowed to double-dip on tree depth - that is indent only one level for like 3 levels of tree, as long as the opening of that 3-deep jump is a clear one-liner and the end of it is a line that says ")))".

  Foo(Bar(Baz(
      someVal
  )));
- when splitting lines on infix operators, anything but commas go at the start of the line (also, bedmas counts as tree depth)

  foo
      + bar
      + baz
      + (
          var1
          * var2
          * var3
      )
      + quux;
loading story #42770572
loading story #42766374
loading story #42766638
loading story #42763804

    (tree-transform-if predicate
                       transformer
                       (second tree)
                       depth)
> A problematic function indentation

> Nineteen! Nineteen spaces of indentation! It's getting unruly.

Why, is there a shortage of whitespace these days??

> Such an indent, when used in deeply nested code, makes it too wide and unreadable.

Ah, I see! Well, I would argue the problem is not the indentation style. What makes your code unreadable is that it's too deply nested, and I would bet that no indentation style can help with that.

loading story #42771808
loading story #42765524
loading story #42763112
loading story #42771462
loading story #42765384
loading story #42767296
loading story #42765651
loading story #42763531
loading story #42763314

    > (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.

I think this is more a problem with languages that encourage (or don't discourage) deeply nested function calls. I face the same issue in Python, although Python kinda discourages me to do:

    foo(
        bar(
            baz(
                far(
                    faz(...)))))

Instead, Python and Pythonic idioms encourage a "step by step" approach:

    a = faz(...)
    b = far(a)
    ...

I don't know which is "better"—the first approach requires thinking ahead about the chain of functions we're about to call while the second one is more like a gradual development of the solution, albeit being more verbose than the former.
loading story #42764067
loading story #42763545
loading story #42763555
loading story #42764609
loading story #42765300
loading story #42763171