返回资讯中心
外部精选
软件工程
#社区热议

What makes Lisp difficult to read?

The Lisp family of programming languages is infamous for their parenthesis-heavy syntax. Many people, myself included, find this syntax more difficult to read than the notation of languages like Python, Java or C. The p…

Hacker Newsibobev2026/9/28 · 2026/9/2830 分钟阅读

以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。

The Lisp family of programming languages is infamous for their parenthesis-heavy syntax. Many people, myself included, find this syntax more difficult to read than the notation of languages like Python, Java or C. The perceived unreadability of Lisp is significant enough that multiple attempts have been made to create new notations to aid Lisp’s adoption. Yet, despite these attempts and Lisp being nearly as old as computing science itself, I have not yet encountered a fully satisfying explanation as to why Lisp is perceived as being less readable.

Lisp developers commonly argue that this is simply an issue of familiarity; Lisp notation is less common than C notation and programmers are therefore less used to reading it. Once you get used to programming in Lisp you don’t even notice the parentheses, allegedly. I believe there is more to it, and that with a deeper investigation we may gain insight into how to design pleasant syntax. In this article I will explain my theory as to why Lisp’s notation – specifically its placement of parentheses – requires more effort to parse, inspired by some observations from cognitive science.

For those who might not be familiar with Lisp’s notation, in a typical Lisp program every statement, expression, and function call is written with the same syntax: (operation arguments...). Compare the definition of a recursive factorial function in Lisp (Scheme) and an equivalent function in JavaScript:

(define (factorial n) (if (= n 1) n (* n (factorial (- n 1)))))

Lisp-style factorial definition in Scheme

function factorial(n) { if (n == 1) { return n; } else { return (n * factorial(n - 1)); } }

C-style factorial definition in JavaScript

You will notice that the Lisp sample lacks infix notation (n == 1), keywords (else) and different types of delimiters ({}). These differences tend to receive most attention in discussions of readability. While I agree that these notations have some effect on readability I suspect their importance is overstated; beyond familiarity I do not find (- n 1) to be clearly inferior to (n - 1).

The syntactical variance that C-style languages have in these different forms of notations for applications does likely significantly influence readability. I intend to investigate this aspect in a different post. In this post, I will focus primarily on the differences that remain when comparing normal function applications. I.e., why does operation(arguments ...) appear to be more pleasant than (operation arguments...)?

To see why placement of parenthesis matters, we have to consider that human perception does not treat all input equally. To experience this, try to say out loud the display colour of the words below as quickly as possible:

You will find that the second row – where the colour of the text does not match the written colour – is slower to read. This is known as the Stroop effect. Several such effects have been discovered which may hinder or aid visual processing tasks. I like to think of these effects as a kind of hardware acceleration built into our perception machinery.1 As with programming, we should attempt to make the best of the capabilities of our hardware.

For instance, consider the two visualisations of the same dataset below. I have hidden two outliers in the data. One of these visualisations uses a ‘hardware acceleration’ trick to make it easier to find the outliers:

You will no doubt be able to spot the outliers in the second visualisation much quicker than in the first. In fact, for the plain data presentation the search tasks takes about 𝑂(𝑛), whereas the hardware accelerated search miraculously terminates in 𝑂(1). Good designs use this effect to make their interfaces more searchable via e.g. differently shaped icons and highlight colours.

I have chosen the Stroop and pop-out effect to illustrate human hardware acceleration since these effects are pronounced and easy to reproduce. Unfortunately, findings from the cognitive sciences are not all as apparent or well-defined. Application of such effects require a degree of creativity and subjectivity. It is therefore not my intention to suggest that readability is entirely objectively explained by the to be discussed cognitive effects.2 Rather, I invoke these effects to serve as design principles and to support my own observations.

Returning to notation, the main goal of syntax is to indicate how tokens should be grouped together. Gestalt Psychology has described several ways in which we are predisposed to perceive groupings. One of the most concrete of these is the Gestalt law of proximity. This ‘law’ is the observation that objects which are close to each other are perceived to be related. For example, below are again two presentations for the same data. Find a way to divide the data points in two groups:

The second visualisation clearly suggests that the data consists of two groups partitioned along the x-axis. The ‘hardware acceleration’ described by the law of proximity makes this partitioning quicker to perceive than in the raw data.

Due to the spacing created by the placement of parentheses and commas, C-style notation more often obeys the law of proximity than Lisp-style notation. C-style function calls with only one argument require no space characters. This allows such calls to be perceived as a single visual group. In contrast, calls with multiple arguments separate each argument by a comma and space character. Lisp-style calls have no varied spacing; every function and argument are separated by the same space character. The spacing thus does little communicate grouping.

For instance, consider the two identical function calls in alternate notations. The third and fourth samples are blurred versions of the first two, to highlight to the proximity effect:

(AAAAA (BBBBB (CCCCC)) (DDDDD) (FFFFF (GGGGG (HHHHH))))

Lisp

AAAAA(BBBBB(CCCCC()), DDDDD(), FFFFF(GGGGG(HHHHH())))

C

(AAAAA (BBBBB (CCCCC)) (DDDDD) (FFFFF (GGGGG (HHHHH))))

Lisp (blurred)

AAAAA(BBBBB(CCCCC()), DDDDD(), FFFFF(GGGGG(HHHHH())))

C (blurred)

Notice that the C snippet consists of three blobs when blurred. These blobs communicate an approximate structure of the code, even in peripheral vision. In the Lisp sample all seven identifiers blur to a separate blob; there is no indication of grouping in peripheral vision.

Two other Gestalt laws related to grouping parentheses are the laws of closure and symmetry. The law of closure states that fragments of a shape are perceived as a single entity by mentally filling the gaps between the fragments. I.e., two parentheses form a visual grouping because they form a single ellipse. The law of symmetry simply states that symmetrical components (e.g., delimiters) are perceived as being part of the same visual group.

Law of closure: a single shape (circle) is perceived by filling in the gaps between its components.

Law of symmetry: symmetrical shapes are perceived as beloning together (subsuming the law of proximity)

To benefit from these grouping effects, the shape of matching parentheses must be clearly distinguishable. This is actually not the case most of the time. The part of the retina where the resolution of light receptors is highest accounts for only about two degrees of vision. This means that at 70 centimetres from a monitor, only an area around 2.5 centimetres in diameter is sharp enough to read comfortably. Your brain hides this blurry text but you can see it if you try to read a few words ahead without moving your eyes.

Since Lisp puts the function name after the opening parenthesis, there is more distance between the parentheses. This makes it less likely that the parentheses are close enough to be scanned as a single shape. The extra distance can be quite significant as Lisps tend to have long function names like make-string-output-stream and call-with-current-continuation. To illustrate, compare the call below in C and Lisp notation. I have applied a radial blur around the opening parentheses to exaggerate the eye’s resolution fall-off towards peripheral vision:

(long-function-name argument)

Lisp-style

long-function-name(argument)

C-style

In the C-snippet the closing parenthesis is visible from the opening parenthesis and can be matched visually via the law of closure and proximity in a single glance. The Lisp-snippet requires remembering the opening parenthesis until the closing parenthesis comes into focus.

The extra distance between parentheses in Lisp is even larger when comparing with C notation for language primitives like conditionals and loops:

(if condition consequent alternative)

Lisp-style

if (condition) {consequent} {alternative}

C-style

The C-style if has more delimiters, yet it is easier to read because the delimiters are much closer to each other.

Some languages allow the delimiters of the last argument to be written after the function parentheses. This alternate notation also places matching delimiters closer to each other:

repeat(5, { body })

Generic Kotlin notation

repeat(5) { body }

‘Trailing lambda’ Kotlin notation

text(fill: red, [hello world])

Generic Typst notation

text(fill: red)[hello world]

Trailing Typst notation

In my experience, the notation with closer delimiters is used much more frequently. I count this as empirical evidence that programmers do prefer parentheses to be close to each other.

To illustrate, compare these alternate formattings of the closing delimiters. On the left we put every closer on the same line resulting a difficult to count block of parentheses. On the right we put each parentheses on its own indentation level. In this formatting we can see that the number of opening and closing parentheses match without having to count them.

(f (g (h (i (j)))))

Lisp formatting

(f (g (h (i (j) ) ) ) )

Java formatting

And in a more ecologically valid program:

(define (factorial n) (if (= n 1) n (* n (factorial (- n 1)))))

Lisp formatting

(define (factorial n) (if (= n 1) n (* n (factorial (- n 1))) ) )

Java formatting

Mental Stack

Next to these visual considerations, readability is also influenced by quirks of human memory. Working memory can only store up to three to five items. You will notice this limit if you try to reverse your phone number in your head. When reading a nested expression we have to remember the context of each expression. This is not unlike the stack in a parser or evaluator. I therefor like to think of the working memory as the mental stack. Notation should attempt to use the mental stack as little as possible as it can only store a handful of items.

If we ignore the visual tricks mentioned before, we can say that in general each opener needs to be remembered until it is closed. This memory requirement is likely what makes deeply nested expressions difficult to read. Since we can only remember ~4 parentheses this means that we cannot read a nested expression with more than that number of levels without extra effort.

In general there is nothing that can be done about this; a complicated expression is difficult to read. However, certain shapes of nesting can be indicated without requiring visually nesting the expression. For example:

(((f a) b ) c)

Pseudo-Lisp

f(a)(b)(c)

Pseudo-C

f[a][b][c]

Pseudo-C, array access notation

In the C syntax, nested application may be notated by writing the outer application directly after the inner. The nesting is given by the left associativity of application syntax. When reading this syntax we do not have to remember and match the parentheses. (Also, the lack of nesting in the C-syntax places the opening and closing parentheses closer to each other, making these easier to match visually.)

Nested applications of the form f(a)(b) are not too common in C-style programs (Scala being the exception), but nested array accesses f[a][b] are ubiquitous. I consider these notations to be essentially the same, the only difference being that the square brackets indica

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →