Fixing S-expressions: overnesting on the left

Darius J Chuck

2021-06-29

This post is inspired by a recent discussion on Hacker News.

Previously we examined how we can improve CSV with a few simple syntactical adjustments.

Today we’ll take a look at one improvement that can be applied to another simple, but much more powerful notation: S-expressions.

They are likely the oldest syntactically-preserved high-level programming notation still in use. Their primary application is representing code and data in the Lisp family of languages, where they originated. More broadly, they can be used to represent any kind of tree-like structures.

TAO can be considered a generalization of S-expressions.

The issue

The issue that we’ll fix here is related to the use of S-expressions as syntax for code. Quoting the Wikipedia article:

When representing source code in Lisp, the first element of an S-expression is commonly an operator or function name and any remaining elements are treated as arguments.

For example:

(f x)

is analogous to:

f(x)

in the the classical functional notation known from mathematics or the most popular programming languages.

Lisp notation works well for simple operators, but for complex ones it suffers from overnesting.

Compare:

(((f x) y) z)

with:

f(x)(y)(z)

This is relevant for curried or partially-applied functions.

The solution

The solution becomes clear: move the operator of an expression outside the opening paren.

This also has the effect of making Lisp code look more familiar to most programmers.

Compare the Wikipedia example before:

(defun factorial (x)
  (if (zerop x)
    1
    (* x (factorial (- x 1)))))

and after applying the solution:

defun (factorial (x)
  if (zerop(x)
    1
    *(x factorial(-(x 1)))
  )
)

Here another purely stylistic change was applied to achieve even more familiarity and reduce the number of consecutive closing parentheses. I.e. the closing paren of each multiline expression was placed on a separate line, with the same level of indentation as the opening paren.

With this small change two problems were solved:

At the same time the minimalism of Lisp was not lost. A net win in any case.