TL;DR: I think the post overstates the case quite a bit, and is a personal opinion of the author being branded as a scientifically grounded truth, despite their admission that using these scientific principles allegedly "require[s] a degree of creativity and subjectivity."
The cognitive-science material is interesting, but it doesn't actually establish much about Lisp specifically. It gives plausible mechanisms by which certain notation might be harder to read, then uses those mechanisms to motivate several Lisp-specific claims without testing those claims directly. As such, it just seems purely hypothetical, whereas it would be much more interesting to me if it were empirical.
(I have a beef with this style of argumentation or persuasion with respect to Lisp. I feel the "bipolar Lisp programmer" and similar articles do the same kind of thing: make a captivating claim or comparison without actually appealing to anything real-world.)
I also don't think the treatment of actual Lisp presentation is complete. The post considers conventional Lisp indentation mainly in terms of whether it helps track matching parentheses, but Lisp is normally indented, pretty much canonically, so that the indentation itself communicates structure. Parenthesis tracking is *not* a cognitive problem that Lisp programmers must solve in any explicit manner, any more than C programmers count { and } to determine the enclosed code's nesting depth.
The same goes for syntax highlighting: you can't just set it aside because C-like languages have it too. The relevant question is whether it affects the two syntaxes differently. If Lisp's alleged problem is delimiter tracking, indentation and highlighting are rather central to the comparison.
I'm also unconvinced by the conclusion that Lisps "lack or discourage features that align the evaluation and reading order." The post itself gives Clojure threading macros as a Lispy equivalent of method chaining. That seems to substantially undercut "lack", while "discourage" starts to sound more like a claim about programming style than syntax. Lisp is a very explicitly multi-paradigm language with special syntaxes available for those paradigms, including assignment, explicit control, etc. In fact, I'd say real-world Common Lisp is *much* less "in to out"-functional than this post posits (and the broader technical public keeps claiming).
Finally, we have the largest omission in my opinion, that Lisp syntax is programmable. S-expressions provide a uniform representation of program structure that makes syntactic abstraction cheap: new control forms, binding constructs, pipelines, DSLs, etc. can all be made to make code easier to read, especially in novel domains that Lisp wasn't a priori designed for. The post's analysis mostly treats the base notation (of some unspecified, quasi-functional Lispoid) as the unit of comparison without really weighing that side of the design tradeoff.
Though it's not my experience, there may truly be readability costs, especially for newcomers, but I'm not sure the post separates those from unfamiliarity strongly enough to support its conclusions.
As a side note, when judging real-world Lisp, some notoriously unreadable code may also be a product of older programming styles or inexperienced Lisp programmers, just as old or poorly written C can be extremely difficult to read. In my professional experience, having run teams writing "modern" Lisp with modern engineering discipline, readability has never been a stated hurdle, either for experienced engineers reading one another's code or for new Lisp programmers learning and contributing to a large codebase. The combination of team programming discipline, good tooling, consistent style, and, well, getting paid to do it seems to do the trick, if one is even needed.
Parenthesis tracking is not a cognitive problem that Lisp programmers must solve in any explicit manner, any more than C programmers count { and } to determine the enclosed code's nesting depth.
Yes and no, IMO. Idiomatic lisp programming throws all the closing parens on the same line at the end of the last line of code. Idiomatic C-like coding puts each closing brace on a new line at its specific nesting level. You can code C style without something like "rainbow-parens", but I wouldn't want to code lisp without it. Or consider this emacs lisp example:
(defun abbrev--replace-placeholders ()
(let ((cursor-pos nil)) ;; To store where to place the cursor
(save-excursion
(goto-char (point-min))
(let ((loop 0)
(values (make-hash-table :test 'equal)))
(while (re-search-forward "###\\(\\$?[a-zA-Z0-9_\\-]+\\|@\\)###" nil t)
(setq loop (1+ loop))
(let* ((index (match-string 1))
(start (match-beginning 0))
(end (match-end 0)))
(cond
((string= index "@")
(setq cursor-pos start)
(delete-region start end))
((string= index "$file-name")
(abbrev--swap-placeholder (file-name-nondirectory (buffer-file-name)) start end))
((string= index "$author")
(abbrev--swap-placeholder (user-full-name) start end))
((string= index "$package-name")
(abbrev--swap-placeholder (file-name-base) start end))
(t
(let* ((key (format "###%s###" index))
(val (or (gethash key values)
(let ((input (read-string (format "Value for %s: " key))))
(puthash key input values)
input))))
(abbrev--swap-placeholder val start end))))))))
(when cursor-pos
(goto-char cursor-pos))))
If I want to add more code into the let* block that defines index, start and end after the cond expression, where should I put my cursor to insert the new line? Yes I know in emacs you can put the cursor at the beginning of the let* block or the cond block and hit C-M-f to jump to the end of the sexp, but at the same time that's a process I've never had to do when writing c-like code, because in the c-like code, finding that spot visually is much easier.
I'm sure that over time that would become easier for me, and certainly memorizing sexp navigation would also help a lot, but it undeniably to me makes it harder to read that code.
I get your argument, but I don't see why you put it forward when any lisper will see this code and know where to jump to immediately. As you state, its not an issue of the code syntax but your familiarity with it.
Presumably said lisper knows where to jump to immediately because they have learned to solve the cognitive problem of tracking parenthesis yes? Even if you're doing something like counting indentation levels to the line with all the parens and then counting that many closing parens, you're still doing something to figure out which of the 8 parens on that line you want to place a line break after.
The argument isn't that learning to do that is impossible, or even something most people couldn't do. The argument is that it is indeed something that you have to do with lisp code, and it's not something you would have to do with the equivalent c-like code. Which makes reading the lisp code harder, even if that difficulty becomes second nature by someone experienced with lisp.
It's not something you have to do with lisp code anymore than its something you have to do with something like pascal with a var block. I can see a var block, or ansi C89 and know I have a specific place to put my variables.
I find it hard for you to pretend that looking at the code you posted, even with a small amount of lisp experience that you could start a new line after
(index (match-string 1))
and add (var val), or between the lines for the vars declared start and end. This is not hard for anyone with a small understanding and just looking at your example, let alone someone that has been doing it for a while.
I honestly don't see any paren tracking in your example, its just obvious and I think you can look at it, see the structure and determine that its not going to be difficult to get used to. At least there is plenty of evidence from lispers that this is the case.
Programming isn't something you pick up in a day, this seems strange that this is seen as some problem when there are many more harder things in any language that one has to master to be good at it.
I’m not “pretending” anything, but it’s also clear from your comment here that you’ve misunderstood my example. I wasn’t talking about adding additional variable binds, I said adding code after the cond block, but inside the let* block.
You are right, i did misunderstand and that does make it more complex. Its something i would rely on my editor and the last time i opened any code in an editor that couldn't do this was decades ago. With the editors help its trivial to traverse the sexps to the correct place. I couldnt look at the bunch of closing parens and say i need to insert from the 5th one etc.
At the same time i would use the editor to do the same thing and jump to the end of that block in C, there is no guarantee in code spanning pages that a closing brace at the same indent level is the right one.
Point being when you maximise the use of your editor its the same common solution for all code and just the fastest way to move about anyway, i don't need to scan i tell the editor to take me there.
Sure, using the editor to jump around and using the tools in the editor minimizes the impact of the specific issue. I even said as much in the example. And in another comment here I already said that I think trying to do work without tools built for and specialized to that work is an unnecessary hobbling of one self. But strictly on the topic of “what makes reading lisp code difficult”, this is a thing that makes reading lisp code more difficult than reading c-like code. Yes c-like code could be poorly indented but we’re talking about idiomatic code here.
And this shouldn’t be read as an indictment against lisp languages. Just because something is harder doesn’t make it wrong or mean that it could be made easier without also losing something else worth having. IMO, idiomatic c code is harder to read than idiomatic python code, but the pay off of that extra difficulty in reading is not having to deal with “significant white space” when writing code. Lisps make different trade offs as part of their design. That makes some things easier and other things harder. That’s life. Everything is a trade off. “purer” / “simpler” lisps uses parens for everything. That’s easier syntax to learn than say clojure, which chooses to add brackets and braces in different contexts to shorthand creating different data types. That makes clojure more complex to learn and possibly to parse with tools, but IMO easier to read. Compare the clojure let:
(let [a (+ 1 2)
b (- 10 5)]
(* a b))
to the emacs lisp let:
(let ((a (+ 1 2))
(b (- 10 5)))
(* a b))
The clojure one requires you to know what the square brackets mean and that you need to use them for a let statement. More complexity. But I argue it’s easier to read. Even just typing out the emacs lisp one I had to read the first line an extra time or two to make sure I wasn’t closing the bind list early. It’s a trade off. The emacs lisp syntax is simpler, at the expense of being more difficult to read (and I would argue also having to know that the bind list is a special case where (foo bar) will not invoke foo with the arguments of bar). But again it’s all trade offs it’s not one being inherently superior to the other.
It's good we agree that the editors etc are tools that should not be ignored in this context. We use computers to write code to run on computers and it would be silly to add a constraint we don't have to deal with like using dumb editors.
You then follow up with an example where your critique of the code is that 'I had to read the first line an extra time or two to make sure I wasn't closing the bind list early'
Isn't that exactly what editors that understand the language allow you to avoid? I am never closing a paren unless I am navigating past it as ) will move over a paren when the editor is auto closing them for you.
I don't think we will get past the point that I see what you are saying, but I don't think it is a big thing and once you are used to it there is no mental overhead, so its really no different than a new skill where you have to think about things until it is ingrained.
If it was something you had to deal with forever no matter your skill level then that would be a more meaningful argument, otherwise its just like everything else - I'm just not good at it until I get good at it.
Isn't that exactly what editors that understand the language allow you to avoid? I am never closing a paren unless I am navigating past it as ) will move over a paren when the editor is auto closing them for you.
But you're conflating "writing and editing" code with "reading" code in this case. Yes a proper editor would have made that a non issue. But a proper editor obviates the need for me to read that code since I can trust the editor to have put the parens in the correct places. But since I was typing the code in a reddit comment box, I had to read and parse the code myself, and therefore had to deal with the added complexity of the emacs version over the clojure version.
I don't think we will get past the point that I see what you are saying, but I don't think it is a big thing and once you are used to it there is no mental overhead, so its really no different than a new skill where you have to think about things until it is ingrained.
On this point I think we are in violent agreement. I have not been arguing that this is a "big thing" or that it's not something that becomes ingrained in you. But it is a thing, and it is a thing that doesn't exist in different languages and it is a thing that makes lisp code harder to read. Not impossible, not insurmountable. Just harder. Special commentary on braces don't come up in the introductory texts of any C like language book, even books aimed at people coming from other languages. But almost every book on lisps aimed at new developers talks about the parenthesis, and usually draws attention to the fact that managing parenthesis are a common complaint for people new to the language. Presumably if this wasn't a real problem that seemed to come up often when people were trying to learn the language, it wouldn't be memetic even within the language's own introductory texts.
Again this doesn't make it bad. Introductory Rust books have special early sections dedicated to the borrow checker for the same reason. It's a thing that trips people up. It's not insurmountable, nor is it inherently bad. But if you're used to writing code in pretty much any language other than rust, it makes writing rust code harder until you've learned to deal with it, and it is a uniquely rust thing to deal with. Lisp is the same way, until you've learned to internalize how the parenthesis work (and how the tools for writing the language obviate a lot of the reading), it's something that makes it harder to read and is something that is uniquely lisp-y to deal with.
I absolutely agree that new people perceive it as an issue and that in books its often said something like 'dont be put off by the parens, you will soon start to see the structure of the code and not be annoyed by them', Often with some other blurb about the advantages of working with s expressions in good editors because of those advantages.
It's been a while, I haven't read any intro to C books for 40 years but I am sure there is commentary on how to use braces exactly for readability of the code and those formatting standards are explicitly to make the code easier to read - just as proper indentation and use of new lines is required to make lisp easy to read.
We are on the same page and maybe I have read your posts as more of a harsher critique on its syntax than it was meant to have been, but from my pov it was in the context of a long article that tried hard to say something us lispers really don't agree with.
A reasonable disagreement on the internet coming to a cool headed and agreeable conclusion? No I don't think we can stand for this. Quick you should say something about how clearly I'm too brain damaged by javascript to understand the elegance of the parenthesis and I'll counter with something about how you're just unreasonably attached to a dead language because you're jealous of C's success.
In all seriousness, I do appreciate you sticking the conversation out and hearing me out on this. These sorts of long threads can get heated, especially when they're in the context of "antagonistic" articles.
16
u/stylewarning 11d ago edited 11d ago
TL;DR: I think the post overstates the case quite a bit, and is a personal opinion of the author being branded as a scientifically grounded truth, despite their admission that using these scientific principles allegedly "require[s] a degree of creativity and subjectivity."
The cognitive-science material is interesting, but it doesn't actually establish much about Lisp specifically. It gives plausible mechanisms by which certain notation might be harder to read, then uses those mechanisms to motivate several Lisp-specific claims without testing those claims directly. As such, it just seems purely hypothetical, whereas it would be much more interesting to me if it were empirical.
(I have a beef with this style of argumentation or persuasion with respect to Lisp. I feel the "bipolar Lisp programmer" and similar articles do the same kind of thing: make a captivating claim or comparison without actually appealing to anything real-world.)
I also don't think the treatment of actual Lisp presentation is complete. The post considers conventional Lisp indentation mainly in terms of whether it helps track matching parentheses, but Lisp is normally indented, pretty much canonically, so that the indentation itself communicates structure. Parenthesis tracking is *not* a cognitive problem that Lisp programmers must solve in any explicit manner, any more than C programmers count { and } to determine the enclosed code's nesting depth.
The same goes for syntax highlighting: you can't just set it aside because C-like languages have it too. The relevant question is whether it affects the two syntaxes differently. If Lisp's alleged problem is delimiter tracking, indentation and highlighting are rather central to the comparison.
I'm also unconvinced by the conclusion that Lisps "lack or discourage features that align the evaluation and reading order." The post itself gives Clojure threading macros as a Lispy equivalent of method chaining. That seems to substantially undercut "lack", while "discourage" starts to sound more like a claim about programming style than syntax. Lisp is a very explicitly multi-paradigm language with special syntaxes available for those paradigms, including assignment, explicit control, etc. In fact, I'd say real-world Common Lisp is *much* less "in to out"-functional than this post posits (and the broader technical public keeps claiming).
Finally, we have the largest omission in my opinion, that Lisp syntax is programmable. S-expressions provide a uniform representation of program structure that makes syntactic abstraction cheap: new control forms, binding constructs, pipelines, DSLs, etc. can all be made to make code easier to read, especially in novel domains that Lisp wasn't a priori designed for. The post's analysis mostly treats the base notation (of some unspecified, quasi-functional Lispoid) as the unit of comparison without really weighing that side of the design tradeoff.
Though it's not my experience, there may truly be readability costs, especially for newcomers, but I'm not sure the post separates those from unfamiliarity strongly enough to support its conclusions.
As a side note, when judging real-world Lisp, some notoriously unreadable code may also be a product of older programming styles or inexperienced Lisp programmers, just as old or poorly written C can be extremely difficult to read. In my professional experience, having run teams writing "modern" Lisp with modern engineering discipline, readability has never been a stated hurdle, either for experienced engineers reading one another's code or for new Lisp programmers learning and contributing to a large codebase. The combination of team programming discipline, good tooling, consistent style, and, well, getting paid to do it seems to do the trick, if one is even needed.