What makes Lisp difficult to read?
ibobev
42 points
75 comments
September 28, 2026
Related Discussions
Found 5 related stories in 89.1ms across 7,945 title embeddings via pgvector HNSW
- A road to Lisp: Why Lisp silcoon · 174 pts · July 09, 2026 · 54% similar
- A Road to Lisp: Which Lisp silcoon · 182 pts · July 17, 2026 · 49% similar
- Lisp as the Maxwell's Equations of Software andsoitis · 65 pts · July 10, 2026 · 46% similar
- That Disgraceful, Disreputable, (Wonderful) Form of Punctuation: The Parenthesis pseudolus · 20 pts · August 19, 2026 · 45% similar
- Claude Is Not a Compiler bryanmikaelian · 148 pts · July 21, 2026 · 44% similar
Discussion Highlights (20 comments)
perrygeo
> I have not yet encountered a fully satisfying explanation as to why Lisp is perceived as being less readable. Difficulty is, by definition, relative to one's skill. You cannot so quickly discount the fact that 99% of programming is taught in Java/C style language syntax. If you've had lifelong exposure to Lisp, you might feel exactly the opposite. The author does a poor job of justifying why these pop-cognitive-psych theories should have more weight than prior exposure. Personally, as someone with decades of exposure to both styles, I look at the factorial example and see everything I love about Lisp syntax - consistent, no magic keywords and syntax to memorize, it represents a tree just like my mental model of code, there's no way to fall through and forget an else, expressions instead of statements, no early returns ... literally everything about the Lisp example is more readable to me. YMMV.
PaulHoule
I tend to struggle with other people's indentation rules for code and other programming languages, like nobody liked my article that said you should carry indentation into embedded strings that represent other programming laguages like SQL result = sql.execute(""" SELECT someColumn FROM thatTable WHERE anotherValue>55 AND state='pending' ORDER BY createdDate """) Ultimately the idea is that indentation should be influenced by semantics, the intention of the code, and not just the syntax. Of course that is against the "one way to indent" philosophy of Go, Biome, and such... But I might accept less than optimal indentation to put an end to tab wars once and for all. I find indentation of Lisp always seems to fail at communicating in the semantics, much worse than other languages. Things like (if condition truePath falsePath) are scrambled when your eye skips over something. If the Lisp community got over its respect for tradition perhaps they'd develop some kind of syntax highlighting or tooltips or something that would clarify this sort of structure. About 90% of real language have subject-verb-object or subject-object-verb orders https://en.wikipedia.org/wiki/Subject%E2%80%93object%E2%80%9... verb-subject-object and verb-object-subject are more Lisp-like and represent 10% of languages including Standard Arabic, Finnish and Fillipino. I like extreme parsimony, like I'd love to write stuff like format = lambda: `%1-%2` but I think as complexity goes up depending on the meaningful order of elements breaks down in many ways and you need to give things meaningful names.
whartung
The parentheses are large glyphs that distract the eye, and do not stand out (to the untrained eye) as delimiters. Consider: (defun split-line (line) (remove-if #'(lambda (word) (string= word "")) (split-sequence:split-sequence #\Space line))) Now imagine if we had some "lower weight" glyph besides the paren. .defun split-line .line, .remove-if #'.lambda .word, .string= word "",, .split-sequence:split-sequence #\Space line,,, Obviously a contrived example replacing () with ., but you can see how "heavy" the parens and how they can dominate what the eye sees. With experience, the parens vanish. The parens being large and common take control of the conversation more than they should.
lukaszkorecki
It's the same thing that makes reading Swift code hard if you never worked with it: lack of familiarity.
ltbarcly3
1. it violates normal syntax rules you have been practicing since you were 4 years old. Lisp will say: (+ (- (\* 3 5) 7) (/ radius pi)) but you are taught since 5: (3*5 - 7 + radius/pi) 2. Humans are highly adapted to using language, and understanding language constructs such as implicit context rules. Humans reduce token counts and structure in favor of implicit rules and making common patterns shorter. Lisp makes them all explicit, which forces you to cope with way more tokens. Lexical binding was added to Common Lisp almost as an afterthought, and the way LET/LET* force you to add layers of nesting demonstrates that. Every time you assign a variable the 'modern' way, you have to indent another block of code. A concrete example is introducing local bindings with actions in between. The thought is "calculate this, do something, then continue": user = find_user(user_id) require_admin(user) report = build_report(user) write_audit_log(user, report) receipt = send_report(report) record_delivery(receipt) In Common Lisp, a direct translation adds a level of nesting for each binding: (let ((user (find-user user-id))) (require-admin user) (let ((report (build-report user))) (write-audit-log user report) (let ((receipt (send-report report))) (record-delivery receipt)))) This is much closer to what is actually happening, and does not require you to understand scoping rules, but it's cumbersome and stupid. LET* handles consecutive bindings, but here the actions must happen between them. You can use PROGN inside the initializers, or introduce dummy bindings for the actions, but either way you're restructuring a flat sequence to fit the binding syntax. That's the implicit context I mean: the statement order combined with syntax rules of the language can supply the scope, without requiring a new enclosing expression every time you introduce a local. Lexical scope itself doesn't require this nesting to be explicit in the syntax of the language and it's not helpful for it to be. 3. So many inconsistencies. Common Lisp uses alternating keys and values for property lists: '(:name "Ada" :age 37) Association lists use a list of pairs: '((:name . "Ada") (:age . 37)) And LET uses two-element binding lists: (let ((name "Ada") (age 37)) ...) Lookup conventions differ as well: (getf plist key) (gethash key table) (assoc key alist) GETF puts the container first; GETHASH and ASSOC put the key first. ASSOC also returns the matching pair, whereas GETF and GETHASH return the value as their primary result. 4. What happens at COMPILE-FILE time is arcane and almost impossible to keep straight. 5. Common Lisp often feels designed primarily to implement Common Lisp, rather than to write useful application code. Its equality predicates are a good example: EQ, EQL, EQUAL, and EQUALP give you four fixed bundles of rules organized around Lisp's own representations. Want arrays compared by content? EQUALP does that, but also makes string comparisons case-insensitive. Want objects compared by their slots? EQUALP does that for DEFSTRUCT instances, but not ordinary CLOS instances. Want to define what equality means for your class? None of these predicates is a generic function you can extend. Checking something basic like "when do these two values mean the same thing?", requires a separate operation of your own. None of the equality operators do anything close to what someone might actually want, unless they happen to be implementing CL in which case they are exactly what you want. Comparison EQ EQL EQUAL EQUALP Lists containing (1 2) NIL NIL T T List (1 2) versus (1.0 2.0) NIL NIL NIL T Strings containing "Ada" NIL NIL T T String "Ada" versus "ADA" NIL NIL NIL T Same-type DEFSTRUCT instances, identical slots NIL NIL NIL T Same-class CLOS instances, identical slots NIL NIL NIL NIL
whalesalad
Personally, I find that a lot of LISP hackers put way too much logic into a single form or method rather than breaking it into smaller components that can be composed together. And I think that's often why we see frustration with LISP is that you'll have something that is indented really deep when it could be a much smaller function that's leveraging other functions. It's even worse in certain lisps like Clojure, where you'll see a lot of shorthand symbolic stuff, like for anonymous functions, or when you're using core.async, where you look at it and you think, is this brainfuck?
meken
I think it has to do more with infix vs. prefix notation - infix is more natural and easier to read.
pfdietz
Lisp isn't difficult to read, so I don't understand the question. This feels like someone who doesn't know French asking "what makes French difficult to read?"
shevy-java
(The(. People say we can ignore them but I find the difficult to ignore. But this is not the only problem with lisp syntax. I found that reading Ruby or Python is simply, on average, so much more efficient. I found scheme somewhat readable - see haxima game world, https://sourceforge.net/projects/nazghul/ it contains scheme files - but I would not want to write any game logic in it.
groundzeros2015
Unfamiliarity.
waffletower
There is so much conjecture in this article -- take the difference between 'lisp formatting' and 'javascript formatting'. I use so-called 'javascript formatting' (a decided and embarrassing misnomer) routinely when I develop Clojure. This article exudes inexperience with Lisp.
delegate
You get used to it after a while and at some point the parenthesis 'disappear' and stop taking up mental cycles. I often thought about this - at first I struggled a lot and wasted so much time trying to match parens, but after some time my brain adapted and then I actually liked the syntax, especially if your editor supports selecting forms or you use something like parinfer, which matches parens based on indentation. Clojure has special syntax for collections of various types, so it's even easier to parse after you get used to it imo. For me, the bigger challenge was wrapping my head around functional programming using immutable data structures, since that wasn't just syntax, it required me to 'unlearn' thinking in OO paradigm and adopting a new way of thinking about how the program works. You get used to that too after a while.
piloto_ciego
I love lisp and think it’s amazing. One of the things that commonly throws me off when I haven’t been writing it very much day to day is sometimes it’s unclear what’s going to be returned by a function. An explicit return 0 is a lot more obvious than just having a 0. Also, I’ll agree that a ‘ or a , in the wrong place is very easy to visually miss, then you can read a totally different meaning out of the code.
tombert
I'll repeat what other people here have been saying: I don't think Lisp is actually "hard" to read, in any objective sense. It's "difficult" to read because it's different that other languages, but I think some of those differences actually improve readability once you understand the language. The forced use of parentheses everywhere guarantees that precedence is never ambiguous, for example.
aaroninsf
Hot take: it's the recursive referential grammar, which does not have an analog in human natural language. Human languages have hard limits on the reference tracking they support, regardless of syntax (marking, positional grammar, etc.). Humans have to reason to unpack LISP (or deeply nested functions or delegation in other languages).
bel8
Lisp is more diffucult to read for most devs because most devs are used to C/Java -like synthax. If most devs were used to Lisp, it would be the other way around. It's a chicken and egg problem, at this point.
boxed
I don't think it's ONLY the parenthesis, it's also the lost opportunity to do meaningful things with indent that bothers me. For example: a missing end paren is often obvious where it should end to a human reader because the next definition starts at some set indent level (0 being the most obvious one). But we talk a different language to the compiler and to the human so the error message is far from where it would be useful. Significant whitespace is actually a good thing™, even if you only use it to narrow compiler error messages. You can still have your parens but also get way better error messages if you just use the indent!
neilv
I have a suspicion that 99.999% of people who say that Lisp syntax is difficult to read haven't really tried to use it (outside of a random homework assignment in CS101). The syntax is incredibly trivial, and needs very little initial instruction (except to tell newbs to stop trying to put the close-paren on a line by itself, like they are fresh out of a JavaScript boot camp and have no frame to conceive anything else, and that, really, the automatic indentation and the shapes should be at least as readable once you try to use it in real-world examples, and see how that works in practice). > 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. Or we can navel-gaze differently, and claim it's more a shape, because then it's a nicely rounded shape that contains the whole function application/call: (foo x y z) foo(x, y, z); There is a small chance that you're doing the following in a Lisp or C, but you'd be doing something fancy that most people do not (like writing the internals for a pluggable framework or object system): (((f a) b ) c) Pseudo-Lisp f(a)(b)(c) Pseudo-C If you were doing this, and you thought the particular code was hard to read, you could throw in a variable or two, for the intermediate function (or function pointer) values. Or use a combinator... When you don't have this fancy function-that-produces-function-that-produces-function pattern, your list head will typically have the name of a function there, and then the syntax simple shape and indentation visually disambiguates. In skimming the article, I did see something the article could've followed through on, for a good syntax criticism of many Lisps: f[a][b][c] Pseudo-C, array access notation Array-intensive Lisp code could use syntax/dialect extension for square brackets for this, if the Lisp doesn't already have comparable syntax: [m a b c] In both references and setters; see my most recent mention of it: https://mastodon.online/@neilvandyke/117067069023420085
z5h
I think the biggest problem is that expressions are lists, but we build lists with quoting, AND unquoting (or quasiquoting). It takes some effort to look at an expression and be certain of what will be run. Are we displaying the instructions to erase the hard drive, or the output of erasing the hard drive. Compare this with Prolog. I like to say "a term is just a term until it's a goal". Assuming we have an erase_hard_drive/1 predicate, I know it's safe to do write_term(erase_hard_drive([force,silent,noprompt]), []) or EraseCommand = erase_hard_drive([force,silent,noprompt]).
adityaathalye
What makes Lisp difficult to read is that almost everyone comes to a Lisp, with deeply ingrained writing skills suited for syntax-heavy, non-structurally-editable languages. --- Now, to die on this hill. Speaking as someone who desperately wants people to stop inventing syntax, I want to offer my "inversions" of the author's conclusions. > prefix notation puts parentheses further apart Prefix notation guarantees that I instantly know what the function / operator is and what the operands are. I don't see (or care) to literally read the parens. The parens are not the point. And if we are doing this, let's compare real-world code shall we? For example, how many syntax points (like = and ; and whatnot) do I not have in this code block (of my dotemacs) because regular prefix notation means I can set any number of global variables in one single declaration? https://github.com/adityaathalye/dotemacs/blob/3ce80d8336ae8... As a bonus, because of the prefix guarantee, when teaching someone a Lisp, I spend almost no time teaching the semantic meaning of syntax rules. All I have to do is show how prefix notation works, and drill that a few times with hand-coding exercises. > formatting practices do not help to track parentheses Auto-formatters---once again, because of prefix notation---can lay out code in very regular patterns and shapes. Auto-formatting further obviates the visual / aesthetic relevance of parens. Not to mention smartparens and structural editing and other basic niceties of Lisp text editors. > prefix notation results in more left-nesting Perhaps. The value for me is that it results in more regular code, because, once again, standard prefix notation, and once again, standard auto-formatting. Nobody reads closing parens, or opening parens, or whatever other parens... we let our text editor take care of that stuff for us. > Lisps lacks or discourages features that align the evaluation and reading order This depends on whether the Lisp in question is imperative, procedural, or functional. It has nothing to do with S-expressions... Essentially, the author makes the usual category error of conflating "Lisp" with "s-expression". May I suggest associating "Lisp" with "Metaprogramming" as the better evil? Die-hard Lisp-2 nerds will balk, but I say any language with an ability to symbolically manipulate itself as its own data is a Lisp. For example, Julia is a kickass Lisp because it enshrined meta-programming as a first-class language design concern. Ditto Elixir. I'm happy thinking of those as M-Expression Lisp-likes. And as a matter of personal taste, having been around lisp nerds, nobody who actively writes s-expression languages---even as hobbyists--- reads parens. They/we see shapes and we manipulate our code like tetris, without even having to think about 'em parens, and soon enough, nor about the keyboard shortcuts. Whether Emacs, or Vim, or VSCode, or IntelliJ or, whatever Lisp-aware editor, we simply slurp and barf and splice and unsplice and convolute-sexp, all of which I do as a normal part of Lisping https://emacsrocks.com/e14.html ... And this stuff is so readily possible because of the regularity of prefix-notation s-expr syntax. (edit: add code example)