10 ms·
OMG, so much this. One of the biggest sources of cognitive load is poor language design. There are so many examples that I can't even begin to list them all h
by lisper 2y ago
OMG, so much this.
One of the biggest sources of cognitive load is poor language design. There are so many examples that I can't even begin to list them all here, but in general, any time a compiler gives you an error and tells you how to fix it that is a big red flag. For example, if the compiler can tell you that there needs to be a semicolon right here, that means that there does not in fact need to be a semicolon right there, but rather that this semicolon is redundant with information that is available elsewhere in the code, and the only reason it's needed is because the language design demands it, not because of any actual necessity to precisely specify the behavior of the code.
Another red flag is boilerplate. By definition boilerplate is something that you have to type not because it's required to specify the behavior of the code but simply because the language design demands it. Boilerplate is always unnecessary cognitive load, and it's one sign of a badly designed language. (Yes, I'm looking at you, Java.)
I use Common Lisp for my coding whenever I can, and one of the reasons is that it, uniquely among languages, allows me to change the syntax and add new constructs so that the language meets the problem and not the other way around. This reduces cognitive load tremendously, and once you get used to it, writing code in any other language starts to feel like a slog. You become keenly aware of the fact that 90% of your mental effort is going not towards actually solving the problem at hand, but appeasing the compiler or conforming to some stupid syntax rule that exists for no reason other than that someone at some time in the dim and distant past thought it might be a good idea, and were almost certainly wrong.
- api 2y agoI agree up until the end. Languages that let you change the syntax can result in stuff where every program is written in its own DSL. Ruby has this issue to some extent.
- lisper 2y agoSure, changing the syntax is not something to be done lightly. It has to be done judiciously and with great care. But it can be a huge win in some cases. For example, take a look at: https://flownet.com/gat/lisp/djbec.lisp https://flownet.com/gat/lisp/djbec.lisp It implements elliptic curve cryptography in Common Lisp using an embedded infix syntax.
- gleenn 2y agoTotally agree. I think the biggest and most important things a language designer chooses is what to disallow. For instance, private/package/public etc is one small example of an imposed restriction which makes it easier to reason about changing a large project because if e.g. something is private then you know it's okay and probably easy to refactor. The self-imposed restrictions save you mental effort later. I also love lisps but am a Clojure fan. This is because in Clojure, 90+% of the code is static functions operating on immutable data. That makes it extremely easy to reason about in the large. Those two restrictions are big and impose a lot of structure, but man I can tear around the codebase with a machete because there are so many things that code /can't do/. Also, testing is boneheaded simple because everything is just parameters in to those static functions and assert on the values coming out. I don't have to do some arduous object construction with all these factories if I need to mock anything, I can use "with-redefs" to statically swap function definitions too, which is clean and very easy to reason about. Choosing the things you mr language disallows is one of the most important things you can do to reduce cognitive load.
- oalae5niMiel7qu 2y agoWhen the code needs to do something that it can't do, there is a massive cognitive load associated with figuring out how to do something approximating it. When the language or a framework is associated with lots of "how do I X" questions, the answers to which are all "completely rethink your program", that is evidence that the language/framework is not reducing cognitive load.
- gleenn 2y agoI'm optimizing for a large, complex corporate projects, not beginners on toy projects. And I would also add that if you search for "how do you do X in Y language", you'll probably find every combination of a lot of languages so I hardly think that is grounds to dismiss Clojure. More and more languages seem to be embracing immutability and static functions are everywhere. The two are just utilized well and believe me, if you need to refactor something then you are so much more at liberty which is certainly a high bar IMHO.
- kvark 2y agoI disagree with the first point. Say, the compiler figured out your missing semicolon. Doesn't mean it's easy for another human to clearly see it. The compiler can spend enormous compute to guess that, and that guess doesn't even have to be right! Ever been in a situation where following the compiler recommendation produces code that doesn't work or even build? We are optimizing syntax for humans here, so pointing out some redundancies is totally fine.
- lisper 2y ago> Doesn't mean it's easy for another human to clearly see it. Why do you think that matters? If it's not needed, then it should never have been there in the first place. If it helps to make the program readable by humans then it can be shown as part of the rendering of the program on a screen, but again, that should be part of the work the computer does, not the human. Unnecessary cognitive load is still unnecessary cognitive load regardless of the goal in whose name it is imposed.
- Calavar 2y agoIn languages (both natural and machine languages) a certain amount of syntax redundancy is a feature. The point of syntax "boilerplate" is to turn typos into syntax errors. When you have a language without any redundant syntactical features, you run the risk that your typo is also valid syntax, just with different semantics than what you intended. IMHO, that's much worse than dealing with a missing semicolon error.
- mckn1ght 2y agoCan you provide an example where syntax that’s required to be typed and can be accurately diagnosed by the compiler can lead to unintended logic? This is not the same thing as like not typing curly braces under an if directive and then adding a second line under it.
- Calavar 2y ago> Can you provide an example where syntax that’s required to be typed and can be accurately diagnosed by the compiler can lead to unintended logic? I'm not sure we are on the same page here. I'm saying the absence of redundant syntax of the sort that lets the compiler accurately diagnose 'trivial' syntax errors, that can create scenarios where a typo can give you unintended valid syntax with different logic. So yes, the conditional shorthand in C would be an example. Getting rid of the braces means you lose an opportunity for the compiler to catch a 'trivial' syntax error, which can lead to different semantics than what the writer intended.
- pwdisswordfishz 2y agoYes, semicolons are totally unnecessary. That’s why nobody who works on JavaScript has ever regretted that automatic semicolon insertion was added to the language. It has never prevented the introduction of new syntaxes to the language (like discussed here: <https://github.com/twbs/bootstrap/issues/3057#issuecomment-5135562 https://github.com/twbs/bootstrap/issues/3057#issuecomment-5...>), nor motivated the addition of awkward grammatical contortions like [no LineTerminator here].
- christophilus 2y agoThere are plenty of languages that don’t require semicolons and yet manage to avoid those issues: Clojure, Go, Odin…
- jmyeet 2y agoClojure delineates everything by explicitly putting statements in parentheses (like any LISP). That's basically the same thing. Go is an interesting example but it gets away with this by being far stricter with syntax IIRC (for the record, I'm a fan of Go's opinionated formatting).
- scotty79 2y agoAlso Scala
- 9rx 2y agoFunny enough, Go’s grammar does require semicolons. It avoids needing them typed in the source code by automatically adding them on each newline before parsing.
- ropejumper 2y agoOn almost every newline, which is the reason why this doesn't work: func thing() { I quite like this approach. It's very simple and consistent, and once you know how it works it's not ever surprising.
- the__alchemist 2y agoGreat points. I strongly agree with your first point. Regrettably, I haven't used any language that solves this. (But believe it's possible, and you've demonstrated with one I haven't used). I'm stuck between two lesser evils, not having the ideal solution you found: 1: Rust: Commits the sin you say. 2: Python, Kotlin, C++ etc: Commits a worse sin: Prints lots of words.. (Varying degrees depending on which of these), where I may or may not be able to tell what's wrong, and if I can, I have to pick it out of a text well. Regarding boilerplate: This is one of the things I dislike most about rust. (As an example). I feel like prefixing`#[derive(Clone, Copy, PartialEq)]` on every (non-holding) enum is a flaw. Likewise, the way I use structs almost always results in prefixing each field with `pub`. (Other people use them in a different way, I believe, which doesn't require this)
- wesselbindt 2y ago> the compiler can tell you that there needs to be a semicolon right here I can see that this is an annoyance, but does it really increase cognitive load? For me language design choices like allowing arbitrary arguments to functions (instead of having a written list of allowed arguments, I have to keep it in my head), or not having static types (instead of the compiler or my ide keeping track of types, I have to hold them in my head) are the main culprits for increasing cognitive load. Putting a semicolon where it belongs after the compiler telling me I have to is a fairly mindless exercise. The mental acrobatics I have to pull off to get anything done in dynamically typed languages is much more taxing to me.
- lisper 2y agoSemicolons are just an example, and a fairly minor one. A bigger pet peeve of mine is C-style type declarations. If I create a binding for X and initialize it to 1, the compiler should be able to figure out that X is an integer without my having to tell it. In fact, all type declarations should be optional, with run-time dynamic typing as a fallback when type inferencing fails. Type "errors" should always be warnings. There should be no dichotomy between "statically typed" and "dynamically typed" languages. There should be a smooth transition between programs with little or no compile-time type information and programs with a lot of compile-time type information, and the compiler should do something reasonable in all cases.
- t-writescode 2y ago> with run-time dynamic typing as a fallback when type inferencing fails. I've seen the code that comes out of this, and how difficult it can be to refactor. I definitely prefer strict typing in every situation that it can't be inferred, if you're going to have a language with static types.
- medo-bear 2y agoIt works the other way too. Ive seen plenty of code with strict typing that could have its cognitive load reduced greatly by dynamic typing. A far bigger problem is hidden sideffects and static typing does nothing to fix that
- bloopernova 2y agoRegarding Common Lisp, do you know of any articles that highlight the methods used to "change the syntax and add new constructs so that the language meets the problem and not the other way around."
- webnrrd2k 2y agoIt's talking about lisp macros, idempotent languages, and a few other features of lispey languages. I'd suggest the book On Lisp, or Lisp in Small Pieces as good places to learn about it, but there are a ton of other resources that may be better suited to your needs.
- webnrrd2k 2y agoAlso check out clojure, and the books: Norvig's PAIP, or Graham's ANSI Common Lisp.
- corinroyal 2y agoAnd don't miss Sonja Keene's book "Object-Oriented Programming in Common Lisp" and Kiczales' "The Art of the Meta-Object Protocol". If you don't reach enlightenment after those, Libgen will refund your money.
- deergomoo 2y ago> Another red flag is boilerplate. By definition boilerplate is something that you have to type not because it's required to specify the behavior of the code but simply because the language design demands it. Boilerplate is always unnecessary cognitive load, and it's one sign of a badly designed language. (Yes, I'm looking at you, Java.) The claim that LLMs are great for spitting out boilerplate has always sat wrong with me for this reason. They are, but could we not spend some of that research money on eliminating some of the need for boilerplate, rather than just making it faster to input?
- JasonSage 2y ago> Another red flag is boilerplate. I have to disagree. Boilerplate can simply be a one-time cost that is paid at setup time, when somebody is already required to have an understanding of what’s happening. That boilerplate can be the platform for others to come along and easily read/modify something verbose without having to go context-switch or learn something. Arguing against boilerplate to an extreme is like arguing for DRY and total prevention of duplicated lines of code. It actually increases the cognitive load. Simple code to read and simple code to write is low-cost, and paying a one-time cost at setup is low compared to repeated cost during maintenance.
- singingfish 2y agoI've had some C# code inflicted on me recently that follows the pile of garbage design pattern. Just some offshore guys fulfilling the poorly expressed spec with as little brain work as possible. The amount of almost-duplicate boilerplate kicking around is one of the problems. Yeah it looks like the language design encourages this lowest common denominator type approach, and has lead into the supplier providing code that needs substantial refactoring in order be able to create automated tests as the entry points ignore separation of concerns and abuse private v public members to give the pretense of best practices while in reality providing worst practice modify this code at your peril instead. It's very annoying because I could have used that budget to do something actually useful, but on the other hand improves my job security for now.
- JasonSage 2y agoSounds like you would have had problems whether there was boilerplate-y code or not.
- singingfish 2y agoThe extra boilerplate noise with excessive repetition doesn't help one little bit.
- oalae5niMiel7qu 2y agoIf some program can generate that code automatically, the need to generate it, write it to disk, and for you to edit it is proof that there is some flaw in the language the code is written in. When the generator needs to change, the whole project is fucked because you either have to delete the generated code, regenerate it, and replicate your modifications (where they still apply, and if they don't still apply, it could have major implications for the entire project), or you have to manually replicate the differences between what the new version of the generator would generate and what the old version generated when you ran it. With AST macros, you don't change generated code, but instead provide pieces of code that get incorporated into the generated code in well-defined ways that allow the generated code to change in the future without scuttling your entire project. >others to come along and easily read/modify something verbose without having to go context-switch or learn something. They're probably not reading it, but assuming it's exactly the same code that appears in countless tutorials, other projects, and LLMs. If there's some subtle modification in there, it could escape notice, and probably will at some point. If there are extensive modifications, then people who rely on that code looking like the tutorials will be unable to comprehend it in any way.
- PittleyDunkin 2y ago> Another red flag is boilerplate. By definition boilerplate is something that you have to type not because it's required to specify the behavior of the code but simply because the language design demands it. Two things: 1) this is often not language design but rather framework design, and 2) any semantic redundancy in context can be called boilerplate. Those same semantics may not be considered boilerplate in a different context. And on the (Common) Lisp perspective—reading and writing lisp is arguably a unique skill that takes time and money to develop and brings much less value in return. I'm not fan of java from an essentialist perspective, but much of that cognitive load can be offset by IDEs, templates, lint tooling, etc etc. It has a role, particularly when you need to marshall a small army of coders very rapidly.
- lisper 2y agoIf the world put even a tenth of the effort into training Lisp programmers as it does into training Java programmers you would have no trouble marshaling an army of Lisp programmers.
- oalae5niMiel7qu 2y agoThe real problem is you cannot ever marshal an army of cheap Lisp programmers, because Lisp programming requires not only learning but raw ability. The big companies are searching for a language that any idiot can learn in a week, with the hope that they can hire thousands of them now, and fire them all next year when LLMs are slightly smarter. They run into the problem that programming is inherently hard, and no amount of finagling with the language can change that, so you have to have someone on every team with actual talent. But the team can be made of mostly idiots, and some of them can be fired next year if LLMs keep improving. If you use Lisp for everything, you can't just hire any idiot. You have to be selective, and that costs money. And you won't be able to fire them unless AGI is achieved.
- lisper 2y ago> you cannot ever marshal an army of cheap Lisp programmers That may be, but since Lisp programmers are easily 10x as productive as ordinary mortals you can pay them, say, 5x as much and still get a pretty good ROI. > you can't just hire any idiot Yeah, well, if you think hiring any idiot is a winning strategy, far be it for me to stand in your way.
- frenchslumber 2y agoCompletely agree! Common Lisp is truly the tool of the Gods.
- nradov 2y agoThe criticisms of Java syntax are somewhat fair, but it's important to understand the historical context. It was first designed in 1995 and intended to be an easy transition for C++ programmers (minimal cognitive load). In an alternate history where James Gosling and his colleagues designed Java "better" then it would have never been widely adopted and ended up as a mere curiosity like Common Lisp is today. Sometimes you have to meet your customers where they are. It has taken a few decades but the latest version significantly reduces the boilerplate.
- lisper 2y agoSure. I understand why things are the way they are. But that I don't think that is a reason not to complain about the way things are. Improvement is always the product of discontent.
- nradov 2y agoImprovement is always the product of submitting a JEP or JSR. https://openjdk.org/jeps/0 https://openjdk.org/jeps/0 https://jcp.org/en/jsr/overview https://jcp.org/en/jsr/overview
- oalae5niMiel7qu 2y agoThe more you contribute to Java, the bigger the problem gets. Java will never die if people keep feeding it, and it'll never be a good language, because that's impossible.
- 3abiton 2y agoMy gripe with the post is that there is no objective "cognitive load" solution. Arguably this varies from 1 person to another.
- epolanski 2y agoI don't think you can have golden rules, if you do, you fall in the usual don't do X, or limit Y to Z lines, etc. But what you _can_ do is to ask yourself whether you're adding or removing cognitive load as you work and seek feedback from (possibly junior) coworkers.
- ants_everywhere 2y agoThis is true for exactly the same reason that no one algorithm compresses all types of data equally well.
- jmyeet 2y agoSo with semi-colons, you have three basic options: 1. Not required (eg Python, Go) 2. Required (eg C/C++, Java) 3. Optional (eg Javascript) For me, (3) is by far the worst option. To me, the whole ASI debate is so ridiculous. To get away with (1), the languages make restrictions on syntax, most of which I think are acceptable. For example, Java/C/C++ allow you to put multiple statements on a single line. Do you need that? Probably not. I can't even think of an example where that's useful/helpful. "Boilerplate" becomes a matter of debate. It's a common criticism with Java, for example (eg anonymous classes). I personally think with modern IDEs it's really a very minor issue. But some languages make, say, the return statement optional. I actually don't like this. I like a return being explicit and clear in the code. Some will argue the return statement is boilerplate. Also, explicit type declarations can be viewed as boilerplate.. There are levels to this. C++'s auto is one-level. So are "var" declarations. Java is more restrictive than this (eg <> for implied types to avoid repeating types in a single declaration). But is this boilerplate? Common Lisp is where you lose me. Like the meme goes, if CL was a good idea it would've caught on at some point in the last 60 years. Redefning the language seems like a recipe for disaster, or at least adding a bunch of cognitive load because you can't trust that "standard" functions aren't doing standard things. Someone once said they like in Java that they're never surprised by 100 lines of code of Java. Unlike CL, there's never a parser or an interpreter hidden in there. Now that's a testament to CL's power for sure. But this kind of power just isn't conducive to maintainable code.
- epolanski 2y agoI like (3) to be honest and the number of times it has poised any issue is virtually 0.
- medo-bear 2y ago> Unlike CL, there's never a parser or an interpreter hidden in there Ive never ever run into this problem in the years of writing common lisp. Can you show me an example code that has this? I wager you cannot and you are writing poopoo about something you know poo about and want it to be true just because you are too lazy to move beyond knowing poo > But this kind of power just isn't conducive to maintainable code. I can usually run code decades old in common lisp. In fact this is one of its well known features. How much more maintainable can it possibly get :)
- cess11 2y agoYou can reduce your Java boilerplate to annotations or succinct XML or whatever. Code generation is used a lot on the JVM. Can you show a real compiler message about such a semicolon?
- lisper 2y ago% cat test.c main () { int x x=1 } % gcc test.c test.c:1:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int] main () { ^ test.c:2:8: error: expected ';' at end of declaration int x ^ ; 1 warning and 1 error generated.
- cess11 2y agoI added int to the main declaration to clean the irrelevant warning, and I get this: tst.c: In function ‘main’: tst.c:3:5: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘x’ 3 | x=1 | ^ tst.c:3:5: error: ‘x’ undeclared (first use in this function) tst.c:3:5: note: each undeclared identifier is reported only once for each function it appears in tst.c:3:8: error: expected ‘;’ before ‘}’ token 3 | x=1 | ^ | ; 4 | } | ~ gcc (Debian 12.2.0-14) 12.2.0 I get three errors, all on line 3 rather than 2, and as the first of them says, there are at least four alternatives for resolution besides semicolon. Full code after adding type to main, including linter message from c.vim: 1 int main () { 2 int x E 3 x=1 /\* E: 'x' undeclared (first use in this function) 4 } .
- epolanski 2y agoThe very same reasons you find CL to lower your cognitive load are why ultimately after 60 years all lisps have been relegated to niche languages despite their benefits, and I say it as a Racket lover. It raises cognitive load for everybody else by having to go through further steps into decoding your choices. It's the very same reason why Haskell monocle-wielding developers haven't been able to produce one single killer software in decades: every single project/library out there has its own extensions to the language, specific compiler flags, etc that onboarding and sharing code becomes a huge chore. And again I say it as an avid Haskeller. Haskellers know that, and there was some short lived simple Haskell momentum but it died fast. But choosing Haskell or a lisp (maybe I can exclude Clojure somewhat) at work? No, no and no. Meanwhile bidonville PHP programmers can boast Laravel, Symfony and dozens of other libraries and frameworks that Haskellers will never ever be able to produce. Java? C? Even more. The language might be old and somewhat complex, but read a line and it means the same in any other project, there are no surprises only your intimacy with the language limiting you. There's no ambiguity.
- lisper 2y ago> But choosing Haskell or a lisp (maybe I can exclude Clojure somewhat) at work? No, no and no. I've been using CL at work for pretty much my entire career and have always gotten a huge amount of leverage from it.
- isodev 2y ago> poor language design We have an excellent modern-day example with Swift - it managed to grow from a simple and effective tool for building apps, to a “designed by committee” monstrosity that requires months to get into.