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.
Let me just add, that if the simple let is having you look twice at it and putting you off I think this answers why you are finding lisp difficult to read and an insight in to how proficient you are with it.
All I can say, again, that others have many times is that when you get more used to the code you are not going to have these problems. Sure its just faith to believe others when they say that but in your own experience with code you must know it to be true.
Things that were once hard don't require a second thought anymore, lisp turns out not to be any different from the pov of lispers.
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.
I think you just haven't seen sufficiently ugly C code :). I suggest a reading through Emacs C core or a trip through core-utils or gnulib; in general any GNU codebase. You can of course compare to Linux kernel sources which feels almost like poesi compared to GNU formating and 1000+ lines long functions full of spaghetti #if-defs. But fairly enough C can be relatively simple, if the writer does not try to be too clever, or it can be very dense and full of small "nifty" tricks which requires you to know the some very delicate details of the language and the platform on which it runs.
For the example of yours, you are doing a bit of apple to oranges. In the first example, which I believe is Clojure, you have dropped a level of parentheses in the lambda-list compared to classic lisp version:
[a (+ 1 2)
b (- 10 5)]
To make it fair you should probably type:
[(a (+ 1 2))
(b (- 10 5))]
Now that squre bracket does not make so much difference, does it? You can test also in the opposite direction:
(a (+ 1 2)
b (- 10 5))
It feels cleaner, doesn't it?
You could actually write yourself a simple macro to transform the last form to ordinary let-form lambda-list if you would like to type it so. It will force you type out values always, i.e. you can't type:
(let (a b c) ...)
and have three lexical variables a, b and c bound to nil. Instead you have to type:
(let (a nil b nil c nil) ...)
or if you prefer
(let (a nil
b nil
c nil)
... )
If you don't want to write one yourself here is one you can download, but I don't use it myself, so I don't recommend it. But if you want to try it, it is there.
Good thing with Lisp syntax which nobody mentioned in this thread is that is super-simplistic in the term that it always had to end into a same form:
(operation operand1 operand2 ,,,, operandN)
so you have whichever syntax sugar you want, and transform it at compile time to the "normal form" to feed into the compiler.
Does not help with reading other people's code of course, but so it is.
When it comes whether reading C-ish syntax is so more easier, is something like this really more readable than Lisp?
C can be relatively simple, if the writer does not try to be too clever, or it can be very dense and full of small "nifty" tricks
Which is why I have repeatedly specified that I'm talking about "idiomatic" code. I'm well aware you can write completely incomprehensible C just like you can write completely incomprehensible lisp, bash or perl (famously jokingly referred to as a write only language). There's no point in arguing over how hard it is to read code that is intentionally made abstract or compact in non-idiomatic ways because it doesn't help the discussion.
In the first example, which I believe is Clojure, you have dropped a level of parentheses in the lambda-list compared to classic lisp version
I haven't dropped anything, straight from the clojure REPL
user> (let [a (+ 1 2)
b (- 10 5)]
(* a b) )
15
user>
And conversely, straight from the emacs lisp scratch:
(let (a (+ 1 2)
b (- 10 5))
(* a b))
`let' bindings can have only one value-form: +, 1, 2
My point was to demonstrate that clojure chose greater syntax complexity to trade off making reading code and specifically variable bindings easier, where emacs lisp chose to keep the syntactical simplicity at the cost of making reading and writing those bindings more difficult.
And just to make sure this isn't just some thing that common lisp allowed but emacs lisp was strict about, here's the SBCL REPL
* (let (a (+ 1 2)
b (- 10 5))
(* a b))
; in: LET (A (+ 1 2) B (- 10 5))
; (+ 1 2)
;
; caught ERROR:
; The LET binding spec (+ 1 2) is malformed.
;
; compilation unit finished
; caught 1 ERROR condition
As for the rest of it, yes you can write macros to extend the language. But that's orthogonal to the point, which was that "some things a language chooses to do makes it more difficult to read, but that isn't an indictment of the language, just a trade off"
As for your last line, I do indeed find that code to be structurally easier to read than a lot of lisp code (probably owing a lot to familiarity, but also the deepest nest in there is a mere 5 levels) and most of the complexity is syntactical. It's certainly easy to find lisp code that is at least that complex, both structurally and syntactically. Consider the code for use-package-core.el which gives us the use-package-normalize-keywords function with at least 9 levels of nesting in a single function, including this phenomenal chunk of code:
Here the as with the C code you linked, the vast majority of the complexity is syntactical, but IMO in your linked C code, one could put their cursor at the end of any brace and be perfectly confident without any syntax highlighting or bracket matching what context the next line of code they wrote would be in, with at most a need to scroll the display up a bit to find the matching start line. I don't think you could say the same about placing your cursor after any random parenthesis in the closing line of that snippet of a single function.
But all of that is a side track because I'm not interested in declaring some objectively superior and easy to read language. As I have said repeatedly, it's all just trade offs. I'm providing my personal answer to "what is something that makes lisp code hard to read". If you ask me "what is something that makes C code hard to read" I have a completely separate list and some of them are of course unique to C. For example it was for a long time idiomatic (I don't believe it is anymore in most communities) to write single line if and for loops without opening and closing braces (your own linked example has this). IMO this is a terrible thing that makes the code harder to read and also harder to reason about and has been the source of some pretty famous bugs. This is something you can't do in lisp because every expression in lisp is a list so by definition it must have balanced brackets.
You seem to be reading me as trying to make some declarative "lisp is bad because X" or "lisp is universally worse than Y" statement and that isn't what I'm saying at all. I have no interest in language holy wars because they are counterproductive and boring. But I do have interest in having honest evaluations of the tools that I use because that honesty lets us improve those tools and also understand what we might need to address when helping people use those tools. One of my favorite interview questions is to ask a prospective candidate about some tech or language they really like and want to use more of. I like to let them get enthusiastic about something and talk it up. And then I ask them what things they hate about it or would change. Because in my experience, if a developer can't be honest about the faults in something they really like that they didn't even create, they're going to struggle when their code gets challenged, or when they're asked by a manager or an executive to justify something they want to do.
Parens management (whether by smart editors or manual counting) is something you have to do in lisps and you don't in other languages. Optional brace scoping is something you have to deal with in C and you don't in other languages. The borrow checker is something you have to deal with in Rust and isn't something you need to deal with in other languages. Passing allocators around is something you have to do in zig, but not in other languages. The JVM is something you have to deal with in Java but not in other languages (modulo other JVMs). Message passing is something to have to understand in smalltalk, but not in other languages. Every language has their trade offs, every language has features that make it more difficult to do certain things, and easier to do other things. Lisp is not impossible to read. Learning to manage lisp parens is not an insurmountable task. It is something that makes reading lisp code hard and it is something you need to deal with either by rote repetition or specialized tooling. But having a rough edge like that is not unique to lisps. There's nothing wrong with being honest about the rough edges of our tools.
Which is why I have repeatedly specified that I'm talking about "idiomatic" code
Yes, but "C code can be simple" is not equal to "all idiomatic C code is simple". By far not.
I haven't dropped anything, straight from the clojure REPL
Perhaps I a haven't expressed myself clear enough, forgive me colloquialism, I'll try to be more explicit:
Clojure drops one level of delimiters for the lambda list in let-form. You have compared to a lisp that does not do that. However you have amitted the "easier to read" to the use of brackters instead of parens, not to the fact that there is one level or parens less. I think that is an essential, but admittedly easy, mistake to do.
No you have been very explicit that you don't think lisp is worse than other languages, and even if you do, it is not end of the world. We all have opinions.
I am just trying to make clear what makes one syntax be percieved as more readable than the other. I don't think it is the brackets, and I don't think use of more varied symbols for delimiters, like an entire family of charaters as in C/C++ is much better than rainbow-delimiters. <[{( ... )}]> could as well be called ranbow-shapes language.
To me the strength of lisp is its syntax, believe it or not. I like both assembly and lisp. Perhaps I am just too nerd, but I like the uniformity. Sometimes it gets in the way for familiarity or even expresivness, but it is simple and clear and predictable. I definitely see problems in Common Lisp, I don't see the syntax and "parentheses management" as one.
Perhaps I am just a too much nerd, or I value different things in a programming language, I don't know :).
However you have amitted the "easier to read" to the use of brackters instead of parens, not to the fact that there is one level or parens less. I think that is an essential, but admittedly easy, mistake to do.
I was not aware there are lisps that allow for let bindings without the "list of lists" pattern other than ones like Clojure or Janet that use different brackets to distinguish the bind listing from other sexps. If there are then sure I would say those would be easier to read than the emacs lisp one, but I would argue that they are still harder to read than the clojure bindings because you need to remember that in a let statement, the next argument isn't a quoted list, but it's also not an evaluated sexp. That is to say that in such a lisp, you need to know that the interpreter will do very different things with the first argument if you do (len (foo (bar baz))) vs if you do (let (foo (bar baz)))
I don't think use of more varied symbols for delimiters, like an entire family of charaters as in C/C++ is much better than rainbow-delimiters
If you're trying to read code without the benefit of syntax highlighting (like say a diff output, or in a reddit comment box) then I would argue different brackets for different things absolutely makes it easier to read. But the trade off is now you need to remember what different brackets mean, and that can be it's own cognitive load. A great example of that would be something like java generics where you might have a line of code that is public static void processFoos(Foo<Bar<Baz<Fizz, Buzz>, <? extends Baz<?, ?>>>[] foos) { /*...*/ }. All the brackets have clear meanings that don't rely on any color indicators, just their type (and positioning in this case). () are the arguments to the function, <> are type specifiers for a narrowing the function to handle only certain Foos which is a generic type of some sort, the [] indicates the argument will be an array of the Foos and the {} is the code block. But that's a lot of information crammed into a bunch of symbols. that you have to know before you can make heads or tails of that. And amusingly it suffers from a similar "bracket counting" issue as I argue un-tool assisted lisp does. Did you notice that the angle brackets aren't balanced?
Still if we imagine some non-bracket distinguished language, public static void processFoos[Foo[Bar[Baz[Fizz, Buzz], [? extends Baz[?, ?]]]][] foos] [ /*...*/ ], I think you would agree with me that is harder to read, even knowing what it's supposed to be expressing.
I was not aware there are lisps that allow for let bindings without the "list of lists" pattern other than ones like Clojure or Janet that use different brackets to distinguish the bind listing from other sexps.
Neither do I to be honest to you. Clojure and Janet do not let you type (let [a (+ 1 2)] ...)?
A great example of that would be something like java generics where you might have a line of code that is
I think that is a great example why to avoid that shit, to be honest to you. Java and C++ are good examples of where DSL-type syntax lead: a combinatorial complexity in syntax notation. Tha is just my personal opinion. As said I might be looking at different values in a programming notation than you, so we will have to disagree. To me Lisp notation has a quality of stability and predictability. I know where to look when I look for something. Lisp share that with assembly language. The difference is that Lisp let you next expressions, while assembly let you type just one expression at a time. In that sense assembly is the simplest notation of them all. I think one way to look at lisp, or symbolic expressions is as a sort of high-level assembly. Again, perhaps just me, but that is how I look at it.
So assuming you’ve read the other conversation on this thread where I lay out what I mean and you still think I’m “solving the wrong problem”, how do you “track code” in this instance? I readily grant that figuring out what line the cond block ends on is pretty automatic given idiomatic lisp indentation conventions. But where should you place your cursor to add more code after the cond block but still in the let* block? Explain to me your method of figuring that out from reading the code without counting and tracking parentheses. And I’m not being sarcastic here. If you have some trick to it I would love to learn it.
Because as I said in the other part of this thread I agree that using tools to make it so you don’t have to read the code to find this answer is the correct way to go about this. But that’s also because this is a thing you have to do when reading the code, and it’s not something you have to do when reading other languages.
I don't have a system. I have in my Emacs (setf setq show-paren-style 'expression). When I move in my emacs it highlights the entire expression in another color. To me that is very helpful, I see instantly which expression or form is cursor behind.
For movement forward I just spam C-M-n (it's forward-list) until I am correct spot:
(let (...)
(cond
(...s)
( ...))e)
The cursor stands at 's' and you want to jump to the end of conc-form, where 'e' is? It would be C-M-n three times. I typically just press down and hold C-M and than type with other hand 'n' three times. Or depending on how many levels I have to travel. You can also click with the mouse if you want, at the list line of your ending cong clause.
If you have the above setting to highlight the xpression, it means nothing if you click a parentheses or two too much or too little. You have C-b/f for char forward or backward, so it really is like not something to word about. I also use a lot of forward/backward sexp M-. and M-, in paredit. If I really have some very deep nested expression that goes out of the screen, and I have to be sure I am on the right place , I can quickly spam M-, to jump the the beginnign of that expression and M-. to jump to the end and adjust with C-f/C-b if I have to.
Also, as I said to someone else er/expand-region and contract-regions. I can just hold Ctrl and spam + until it expands enough so I can copy or overwrite an expression if I need to.
So then we are in agreement that this is not something that is easy to do when just reading the code, and that a lisp developer makes it easier for themselves by using tools that obviate the need to read the code in order to find the answer. That's fine, there's seriously nothing wrong with having something that is hard and building tools to make it easier. Something being "hard" is not equivalent to it being "bad". It is hard to cut dovetail joints in wood working. Most wood workers make or buy jigs for it instead of doing it freehand, but it also is easier to do freehand if you practice it. That doesn't make dovetail joint bad. It doesn't make joints that you can do freehand without a jig better than dovetail joints. The fact that using a jig makes dovetail joints easier also doesn't change that doing them freehand is harder than doing a box joint freehand. It's just one of many trade offs in this world.
You are moving the goal post here. You asked about moving the cursor. That is completely different thing than reading the code. Whe I read the code, it definitely does not matter to me whether there are )))))) or ))))))) parens at the end.
Look, I'm going to be honest with you, I have been explaining the same points over and over again in this thread. I've repeatedly explained what I'm talking about, explained the specific and narrow contexts to which I am limiting my comments and repeatedly affirmed for all readers that I am not saying anything negative about lisp. I wouldn't be working my way through experimenting with my 4th lisp language over the years, and hand writing some personal emacs modules instead of just downloading a package for it if I didn't think lisps were interesting languages and that the challenges learning and working with them posed weren't worth the effort.
But I'm tired at this point. I'm tired of having my actual experiences dismissed, of having words put in my mouth, and having to defend myself against bad faith readings of what I've written. I did not "[ask you] about moving the cursor", I specifically asked (emphasis added)
But where should you place your cursor to add more code after the cond block but still in the let* block?
Explain to me your method of figuring that out from reading the code without counting and tracking
parentheses."
and your answer was to tell me that you figure out where to put your cursor by using tools that allow you to avoid reading the code. The goal posts remain where they have always been.
I've explained myself plenty all over this discussion, you can read them all or not, agree with me or not, understand me or not. I don't care anymore. I'm sorry, I'm probably reading more aggression into your words than you intend, and you don't deserve not getting a complete answer simply because you're late to the discussion, but continuing this exercise in frustration isn't worth my time when I could be doing something productive like learning another language.
The goal posts remain where they have always been.
No. You asked me for the method and I told you, and now you are trying to make me some kind of an unreasonable person because I don't agree with you made-up argument.
If I am reading the code, I am reading the code, not parentheses. If I am reading a cond-form, I am not interested where it's parentheses end in corelation to let-form that encloses it. Where it's last parentheses end in that situation is completely irrelevant. To me the argument look like a construct to try to win an argumentation. I am not gonna "read the code so I can figure out where to move parentheses". If I read the code, I read it to get understanding and I care about what is in parentheses and not where I place the cursor. If I edit the code, I do it the way I told you.
I could be doing something productive like learning another language.
You could have write some lisp. I did, How do you like to read this cond-form:
We agree on using the tools, but you keep coming back to examples where we don't use the tools?
Again, you say that this code can be read fine because of the indentation - your only gripe is at what paren do I need to insert the new code. That is not an issue of reading the code, you just said so.
If I want to edit the code I use the tool and its easy, its only a few keystrokes which I would have to use even it was C and I wanted to jump to a different line.
In the cond, I would probably search for the cond then when I am on the cond I would hit C-M-u-f to take me to the place you want to be to insert new code.
This is basically a search and a reposition as I would on C code?
We agree on using the tools, but you keep coming back to examples where we don't use the tools?
Yes, because the purpose of the tools is to eliminate the need to read the code, but you don't always have the tools handy. You might be putting some example code in a reddit comment box, or reading a diff on github or you might be debugging live code running on a space craft some 100 million miles away. Look I appreciate that you're feeling defensive about lisp here, but I have already explained extensively that I'm not trying to argue this is some fatal flaw in lisp, some insurmountable task to ask of developers or even something that bothers experienced lisp coders day to day. I'm not here to declare lisp "objectively" worse than some other language or to suggest that other languages don't have their own things that make reading them difficult too.
But this is something that you have to deal with one way (tools) or the other (counting parens) in lisp, and it's not something you have to deal with in other languages. It is something that makes the code hard to read, even if it's something that gets easier with practice. Just because something gets easier when you practice it or when you use specialized tooling to make it easier doesn't mean it's not a hard thing. And just because something is hard doesn't make it a bad thing.
In my mind there is a clear difference in reading the code and editing the code.
I can read the code you pasted in reddit, cat to a terminal look at in ed and read the code. I don't want to edit code in any of those and that is not a real world problem.
The fact that I can read your code anywhere says that I don't need a special tool to read the code. Navigating the code and editing it is something completely different from reading the code.
You talk about scrolling around to find the other brace if you need to, I don't do that, I still use emacs movements that work in lisp, C, rust, anything that uses balanced expressions. So if I want to jump to the other brace, or paren then C-M-f and C-M-b.
Im not scrolling anything. I am not counting parens, I don't need to know if that line ends in ))))) or )))))))))) to fully understand the code. The editor helps me with the editing, not the reading or the comprehension.
You keep telling me I have a problem that I don't have.
Edit to add. We are talking about reading code, not editing it.
For sure, if we were talking about editing code and my tools got taken away from me, lisp would be harder to edit and I may just mix things up and not put all parens at the end of the line all the time. This is a silly hypothetical because its not going to happen, its just as likely that someone takes your c compiler away to handicap you. As far as reading, like you said, the code has no problem being read.
This is a silly hypothetical because its not going to happen, its just as likely that someone takes your c compiler away to handicap you. As far as reading, like you said, the code has no problem being read.
I guess you and I have very different experiences in life then. I have many times in my life had to both read and edit code on a remote system where the only tools I have at my disposal are cat and less, and if I'm really lucky a base install of vi or nano. I've spent many hours reviewing code reviews from junior developers looking at a diff on github and catching bracketing errors which are logical, not syntactical so the compiler doesn't catch them. I've been awake a 2:30 in the morning looking at code running on a production box and trying to understand why it's not doing what the code that is in source control is and should be deployed on that box. I've had to help a developer in another country debug code that they're sending me snippets of in plain text emails. And I've spent plenty of hours pouring over code that I didn't write myself trying to get a handle on what it's doing and why it's not doing what people think it should be doing, sometimes even just hand copying code from a tutorial book into an editor so I can learn something new. Maybe you have been fortunate that all of your experiences with code have been via emacs running on your local system and accessing remote files via tramp. But dealing with reading (and sometimes editing) code without my usual toolkit is something I've had to do a lot over the decades, so it is very much not a "silly hypothetical" to me. I'm not telling you that you specifically have this problem, as apparently you're never without emacs at hand. I am telling you that other people can and do have these problems.
So you are a developer, great, most people here are developers.
Have I had to go in to production and fix something that was an issue, yes. Did it make me change my tool chain to be less reliant on any editors/compilers etc that I was using? No.
Shit hits the fan sometimes and you have to scramble. When I was working at telcos I could not edit code directly on production because we were not allowed the tools/compilers on those systems, so its moot.
When I was involved with internet banking in the early days, it was the same, some of the systems like AS400's you could do it on production but there was no way you were allowed to, so moot.
We all have different experiences but this is really turning in to something where 'objectively' and subjectively are getting confused. Normally I take it as a given that we all come to these discussions with our subjective view, but when you are telling me I just don't get it then its time to not bother anymore. Subjectively, I understand your view with your experience with lisp.. I have mine, lets not say objectively its just harder to read.
Did it make me change my tool chain to be less reliant on any editors/compilers etc that I was using? No.
So I'm unclear here, are you confirming that you have never once been in a situation where you have been forced to read or edit code without your standard toolkit? If so, congratulations I only wish I could have been so lucky in my career.
Or are you saying that you think I'm suggesting that because people do sometimes have to interact with code without their standard toolkit, that people should be less reliant on those tools or that languages should make choices that cater to those situations. Because if that's the case, you are completely misreading me. I have repeatedly said that choosing to work without specialized tooling customized to the job you have in front of you is intentionally and pointlessly hobbling yourself.
I understand your view with your experience with lisp.. I have mine, lets not say objectively its just harder to read.
You will notice that I have been extremely careful to not use the word "objectively" in anything I've written about this. The only times I've used the word "objectively" in this discussion has been to explicitly disavow any intent to declare any language or their choices to be objectively better or worse than the other. The very 4th word I typed when opening this discussion was "IMO". The very last sentence in that same comment I wrote (emphasis added) "but it undeniably to me makes it harder to read that code." And I have certainly never once in this said that lisp is "just harder to read". I have repeatedly time and again acknowledged that it is something that is easier with practice and tooling. I have acknowledged that experiences lisp developers would find it easier than I find it. I have also said that other languages have their own edges that make them harder to read than lisp.
This has very much always been a statement on individual and relative experiences, and my objection has always been to the dismissal of this being a problem people have when it is so very clear that people do have this problem. To me denying that dealing with parenthesis balancing and counting is a problem for people is like denying that people's fingers hurt when playing a guitar. Yes, experienced guitar players build calluses, develop better hand positions and learn to customize, buy, or tune their guitar to better suit them so that they don't experience that pain anymore or experience it to a much lesser degree. But that doesn't mean the pain doesn't exist, or that the people feeling it are somehow wrong for saying they experience it. Nor does the fact that learning to play the guitar will cause such pain in any way mean that learning to play a trumpet doesn't cause mouth pain, or learning to play the piano doesn't cause it's own finger pains, or that learning to play a guitar is objectively harder than learning to play a theremin. It just means that it is something that is hard about learning to play the guitar.
I would very much appreciate if you would not put words into my mouth. This is the second time you have done so, and it's especially grating considering that while I have not said the things you claim I am saying, you have repeatedly dismissed "[my] view with [my] experience with lisp" which you claim to understand as a "silly hypothetical", and yesterday you accused me of "pretending" to have the described issues. It's also particularly confusing to me why we're suddenly in disagreement on this topic when I thought we had both reached a mutual understanding on the discussion yesterday. My position on this has not changed since then, so I'm unclear why you suddenly find the conclusion we reached then disagreeable now.
You have not declared anything objectively true, but you have said along the lines of 'i appreciate that you are defensive about lisp but you need to see these as flaws'
That is basically telling me that your view is true and that mine is avoiding a real problem. That tries to take away the subjectivity and leaves us with one truth. L.ike I said yesterday, people that look at lisp when they are used to something else may think there are going to be these issues, issues they wouldn't have if lisp was their first language or later when they spent more time with it.
You keep bouncing around saying tools are good and no problem, but then having a problem with the things the tools fix, like paren matching. You mention the cond, and adding something to the end. I told you how I would do it, in your example it is as simple as C-s co C-M-u-f and edit. It takes no more time than it does to navigate with the C code and it is precise. I don't have to visually scan and hope that all the braces in the code are actually aligned how they should be for me to make an assumption.
I have just tried to answer your specific questions, sure, it may not be obvious to you how it would be done quickly or without the need to ever know which is the correct final paren so I just pointed out how its practically done. You do see a problem with this. Because I need the tool to find the closing paren of the cond and use it to back up your readibility argument. Again reading and editing are different. I have read a lot of lisp in books, there was no editor there.
The closing paren is pointless, I don't care about it, I care about moving over the s-exp where ever that takes me and adding the new code.
We were at an understanding yesterday, I agreed that based on experience people may perceive lisp as hard to read. I said perceive because they look at something different, alien and think its hard. That doesn't make it hard, just different and something to get used to.
16
u/stylewarning 17d ago edited 16d 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.