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.
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.
I'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.
If 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.
I 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.
> 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.
In 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.
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? This is not the same thing as like not typing curly braces under an if directive and then adding a second line under it.
> 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.
Yes, these are different things, which is why I discounted curly braces before. Those are not required for an if statement’s scope. Semicolon’s are “required” everywhere. The compiler can easily spot where one should be by parsing an invalid expression because it encounters illegal tokens to append onto the end of a valid expression, eg you cannot have one statement that contains two assignment operators at the same level of precedence.
However for curly brances around a conditional lexical scope, the compiler cannot tell you where the closing brace should be, besides before the end of the lexical scope that contains it, like the end of the containing function or class. There can be multiple valid locations before that: every other valid line of code. This is not the same as a semicolon, which must end every line of code.
The full stop at the end of your last sentence isn’t strictly needed. It isn’t even strictly needed in the preceding sentences (except for the first one with the question mark), because the capitalization already indicates the beginning of the next sentence. We still use full stops because redundancy and consistency help preventing errors and ambiguities. Reducing the tolerances to zero increases the risk of mistakes and misinterpretations.
Ideally, adding/removing/changing a single character in valid source code would always render the code invalid instead of “silently” changing its meaning.
> The full stop at the end of your last sentence isn’t strictly needed.
yes, that's true | but this redundancy is not necessary | it's there for historical reasons | there are other ways to designate the separation between sentences | some of those alternatives might even make more sense than the usual convention
The point was that the full stop is currently a redundant element in most cases, yet we would not want to omit it just for the reason of being redundant.
The spaces in your “ | ” punctuation are also not strictly needed, yet one would want to keep then for readability and for risk of otherwise mistaking an “|” for an “l” or similar.
Again, something not being strictly needed isn’t a sufficient argument to do without it. There are trade-offs.
The Right Answer is to separate the underlying representation from the rendering. We already do this to some extent in modern systems. For example, the meaning of text generally doesn't change if you change the font. This is not always true. The font can matter in math, for example. And some font modifications can carry semantic information -- using italics can provide emphasis, for example.
The Right Way to design a programming language is to have an underlying unambiguous non-redundant canonical representation, and then multiple possible renderings that can be tailored to different requirements. Again, we kinda-sorta do that in modern systems with, for example, syntax coloring. But that's just a half-assed hack layered on top of deeply broken language designs.
Considering all the "tabs or spaces" flamewars and standardized formatting as with gofmt for Go code, I think this would get restricted at most professional codebases to some person's favored style. Not sure that's a good reason, but it's worth considering. For projects that are solo or along those lines, feel free.
You're being disingenuous. Your suggestion is more like if you wrote
yes, that's true but this redundancy is not necessary it's there for historical reasons...
without any breaks. That might be exaggerating compared to your actual position, but surely you can see that "unnecessary in this situation" doesn't imply "unnecessary overall". "Not necessary" if we're cherrypicking, sure.
If my program now has no semicolons and then I write something else that behaves differently than expected, I'm going to be sad. My mental model for programming fares better when semicolons are used, so I will favor writing programs with semicolons. To me, the cost is trivial and the benefit, while minimal, outweights the cost. I consider it separate from actual boilerplate. You can disagree and use other languages, but then we're probably being moreso opinionated than divided into better or worse camps.
> That might be exaggerating compared to your actual position
To the point of being a straw man.
There was actually a time when neither white space nor punctuation was used andallwordswerejustruntogetherlikethis. Note that it's still possible to decipher that text, it just takes a bit more effort. Natural language is inherently redundant to a certain extent. It's mathematically impossible to remove all redundancy (that would be tantamount to achieving optimal compression, which is uncomputable).
The spaces around the vertical bars in my example were redundant because they always appeared before and after. That is a sort of trivial redundancy and yes, you can remove it without loss of information. It just makes the typography look a little less aesthetically appealing (IMHO). But having something to indicate the boundaries between words and sentences has actual value and reduces cognitive load.
I think you forgot the analogy. Why is it bad to have semicolons in programs then?
> 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.
You said this originally. I definitely agree for something like parentheses in if conditions in Java, but I think semicolons are a great example of how
> having something to indicate the boundaries between words and sentences has actual value and reduces cognitive load.
> Why is it bad to have semicolons in programs then?
It's not bad to have them, it's bad to require them when they aren't necessary. It's bad to make their absence be a fatal syntax error when they aren't necessary. (Some times they are necessary, but that's the exception in contemporary languages.)
Also, I know I'm the one who brought them up, but semicolons are just one small example of a much bigger and more widespread problem. It's a mistake to fixate on semicolons.
Aside from the other good points, this thread is about cognitive load. If a language lets you leave off lots of syntactic elements & let the compiler infer from context, that also forces anyone else reading it to also do the cognitive work to infer from context.
The only overhead it increases is the mechanical effort to type the syntax by the code author; they already had to know the context to know there should be two statements, because they made them, so there's no increased "cognitive" load.
I guess I didn't make this clear. I'm not advocating for semicolons to be made optional. I'm saying that they should not be included in the language syntax at all unless they are necessary for some semantic purpose. And this goes for any language element, not just semicolons.
The vast majority of punctuation in programming languages is unnecessary. The vast majority of type declarations are unnecessary. All boilerplate is unnecessary. All these things are there mostly because of tradition, not because there is any technical justification for any of it.
The point generalises beyond semicolons; everything you leave to context is something other people have to load up the context for in order to understand.
Consider Python; if there are the optional type hints, those can tell you the third parameter to a function is optional. If those are missing, you need to dive into the function to find that out; those type hints are entirely optional, and yet they reduce the cognitive load of anyone using it.
>The point generalises beyond semicolons; everything you leave to context is something other people have to load up the context for in order to understand.
This is not true, because an editor can add any on-screen hints that are needed to help a human understand the code. For example, in my editor, Python code gets vertical lines that indicate where the different indentation levels are, so I can easily see when two lines of code far apart on the screen are at the same indentation level, or how many indentation levels lower the next line is after a long, highly-indented block. Python could add an end-of-block marker like Ruby does to make things like this easier to see, or it could try to encode the vertical lines into the language somehow, but I'd derive no benefit because the editor already gives me the visual clues I need.
I haven’t used type hints in Python, but can what you’re describing lead to situations where the code cannot run and the interpreter gives you a suggestion on how to fix it?
> 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.
That means you and the compiler front-end are looking at different representations. Sounds doesn't sound like a good idea. Keep it stupid simple: all of our source control tools should work on the same representation, and it should be simpler and portable. Well defined syntax is a good choice here, even if there's some redundancy.
> then it can be shown as part of the rendering of the program on a screen
I disagree with this, and can most easily express my disagreement by pointing out that people look at code with a diversity of programs: From simple text editors with few affordances to convey a programs meaning apart from the plain text like notepad and pico all the way up to the full IDEs that can do automatic refactoring and structured editing like the Jet Brains suite, Emacs+Paredit, or the clearly ever-superior Visual Interdev 6.
If people view code through a diversity of programs, then code's on-disk form matters, IMO.
People's choice of editor is influenced by what they're editing. For example, virtually every Lisp programmer uses Emacs, even though there are many alternatives out there, including VS Code plugins. And virtually every Java programmer uses a JetBrains IDE or something similar. I'd probably install an IDE if I had to work on a Java codebase. Editing with a diversity of programs isn't universal.
Sure, but nothing stops you from looking at the raw code. Consider looking at compiled code. You can always hexdump the object file, but have a disassembly helps a lot.
No, as python and other languages amply demonstrate, the semicolon is for the compiler, not the developer. If the compiler is sophisticated enough to figure out that a semicolon is needed, it has become optional. That's the OP's point.
But the language spec for Python is what allows for this, not the compiler. \n is just the magic character now except now we also need a \ to make multiline expressions. It’s all trade offs, compilers are not magic
> now except now we also need a \ to make multiline expressions.
You never need the backslash in Python to make multiple expressions. There's always a way to do multiline using parentheses. Their own style guidelines discourage using backslash for this purpose.
And you can also do it with triple quotation marks if strings are involved, but it’s still more work for the compiler that someone explicitly did, it’s not magic.
Plain strings work fine. Python has the same behavior as C: If two strings separated by whitespace, it concatenates them. So if I have a long string, I start with an open parenthesis, type one string, go to the next line, and another, and so on over several lines. Python sees it all as one string. Very readable.
JavaScript has some specific and unique issues. Some silly choices (like auto inserting of semi-colons after empty return) and source code routinely, intentionally getting mangled by minification.
It's not that the semicolon is somehow a special character and that's why it's required/optional. It's the context that makes it necessary or not. Python proves that it's possible to design a language that doesn't need semicolons; it does not mean that e.g. Java or C are well defined if you make semicolons optional.
If it’s in the language spec as required there and I’m using a compiler that claims to implement that language spec, I want the compiler to raise the error.
Additionally offering help on how to fix it is welcome, but silently accepting not-language-X code as if it were valid language-X code is not what I want in a language-X compiler.
Totally 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.
When 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.
I'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.
>I'm optimizing for a large, complex corporate projects, not beginners on toy projects.
There's nothing about large, complex corporate projects that demands that languages impose arbitrary restrictions on code, except the fact that so many corporations insist on hiring incompetent fools as programmers, often to save money in the short run by expanding the potential labor pool. They call them "guardrails", but a better metaphor would be the playpen. If you hire only competent developers, then you don't need to put them in a playpen.
>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.
Well yeah, it's pretty much the norm in popular programming languages to make certain things impossible. And programming is driven by fads, so we're going to see more and more of this until it finally goes out of fashion one day and some other fad comes along.
Please elaborate what programming language you think isn't a fad and isn't a playpen and why. Restrictions on languages IMHO clearly narrow what you have to think about other code doing. Whether it's marking a field "const" in C++ or having the borrow checker in Rust or having private fields in Java or immutability in Clojure, all those things make it easier to know what a large system of code /cannot/ do, and that makes it easier to maintain. That has nothing to do with people other than it might be you fighting years of code that you wrote yourself.
Yes, 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-5...>), nor motivated the addition of awkward grammatical contortions like [no LineTerminator here].
Clojure 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).
Funny 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.
> 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.
Semicolons 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.
> 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.
It 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
I work in dynamically typed languages a lot, so I don't have many opportunities to feel the way you do. Could you give an example where moving from static to dynamic would reduce cognitive load?
For the opposite example, here's where my preference comes from: I'm editing a function. There's an argument called order, and most likely it's either an instance of some Order class, which has some attribute I need, or it's an integer representing the I'd of such an order, or it's null. I'm hoping to access that attribute, so I have to figure out the type of order.
In a dynamically typed language, I'll have to look at the caller of my function (goes on my mental stack), see where it gets it order from, potentially go to the caller of that function (goes on my mental stack), etc until I hopefully see where order is instantiated and figure out it's type, so I can take the call sites off of my mind, and just keep the type of order in my mental stack.
But actually, this is wrong, because my function is called by way more functions than the ones I examined. So really, all I know now is that _sometimes_ order is of type Order. To be sure, I have to go to _all_ callers of my function, and all their callers, etc. This grows exponentially.
But let's say I manage, and I find the type of order, and keep it in my mind. Then I need to repeat the same process for other arguments I want to use, which is now harder because I'm keeping the type of order on my mental stack. If I manage to succeed, I can go and edit the function, keeping the types of whatever variables I'm using in my head. I pray that I didn't miss a call site, and that the logic is not too complicated, because my mental stack is occupied with remembering types.
Here's how to do this in a statically typed language: read the type signature. Done.
just think of any obvious function where type is obvious. surely writing typing information in those cases is reddundant to the extent that it wouldnt even help with optimisation. but think about something like this:
bool function1(x y z):
bool function2(x y z)
immagine function2 besides returning true/false mutates x in some major way. this is a far bigger and more common problem than typing. a dynamic language with capabilities of runtime debugging is far better equiped to inspect code like this
also i am not saying that typing is worse than no type information. im saying that typing should be optional as far as the compiler is concerned and typing information should be like documentation that is useful to the compiler. common lisp can be an example of a language that is both dynamic and strongly typed (see SBCL) to the extent that you can implement a statically typed language (ala ML) in it
Sometimes when people decry lack of typing, it turns out that it's actually a lack of the ability to define a named structure type with named fields, rather than faking one out with an ad hoc hash table representing a collection of properties.
> a dynamic language with capabilities of runtime debugging is far better equiped to inspect code like this
Have you used a robust, modern debugger, like IntelliJ or Visual Studio? You can do a whole heck of a lot of very, very robust debugging. You can watch, run commands, run sets of commands, inspect anything in great detail, write new code in the watcher, and so on.
I use them and compared to what I can do with my Common Lisp programs on a far less computationally intensive IDE it is many times poorer. An even more interesting aspect because it is entirely people dependent, is that I find code in an average common lisp project far more readable and understandable than what is regarded as a very good java aplication
to further expand on this, in common lisp i can build up my program dynamically in the repl, allowing me to progrssively make more readable and understandable code after making it do what i want, and then do the final tidying up with type i formation amd necessary documantation when i compile it. i fail to see how any i can program with less cognitive load in any other language, especially strictly static ones
there are "repls" and repls. even java has a "repl". forget about dynamic/static dychtomy, im yet to see a non-lisp repl. one which provides a true interactive experience with your running program
and even though static languages can have "repls" as an afterthought (eg GHCi), rest assured that their static typing property is a completely unnecessary cognitive load (at least cognitive but very likely a performance one too) to their functioning
Sure, but often != always. And if your language forbids type inference then you are burdened with the cognitive load of worrying about types every single time whether there is a good reason in that instance or not.
The 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.
This requirement has become a meme. I can do more on a project alone (spanning several new for me domains) with lisp than I can with a group of 5 or 10 in any other language
I’m surprised to hear this from an avid Haskeller and I think it might give the wrong impression to those who are less familiar with Haskell. I’m sure you know this, but for the benefit of others, projects don’t have their own extensions, they just may or may not use some of the extensions provided by GHC. Anyway, that practice is now diminishing given the GHC2021 and GHC2024 “standards”, which just enable a fixed set of stable extensions.
And regarding using specific compiler flags, well, projects almost never do that.
I dumped Haskell specifically because of its enormous cognitive load. All my time and energy went into Haskell and its endless quirks, leaving nothing for the business problem and its stakeholders.
Intriguing! Could you say more about which aspects of Haskell gave it a high cognitive load for you?
By contrast, I've used predominantly Haskell in my career for the last ten years, exactly because it has reduced my cognitive load. So I'm interested in understand the discrepancy here.
Too much experimental software, too much fragmentation in major architectural design approaches, compounded by weak documentation, abandonware, and a tiny community.
Consider the choices in optics libraries, effects systems, regex packages, Haskell design patterns, web frameworks, SQL libraries, and even basic string datatypes. Now consider the Cartesian product of those choices and all their mutual incompatibilities. That's cognitive overload and nobody pays for analysis paralysis.
A stable engineering group with long-term projects can define a reference architecture and these problems are manageable. But consider large enterprise consulting, where I work. We routinely see unrelated new client projects, quickly assembled teams with non-intersecting skill sets, and arbitrary client technical constraints. Here, the idea of a repeatable, reference architecture doesn't fit, and every new project suffered cognitive overload from Haskell's big Cartesian product.
I really hoped Boring Haskell, Simple Haskell, and other pragmatic influences would prevail but Haskell has gone elsewhere. Those values are worth reconsidering, either in Haskell directly, or in a new and simpler language that puts those goals at center of its mission.
Thanks! You seem to be mostly talking about cognitive load arising from having too many choices. Is that right?
(That said, I don't understand what abandonware and a tiny community have to do with cognitive load -- I agree they're bad, I just don't see the connection to cognitive load.)
> I really hoped Boring Haskell, Simple Haskell, and other pragmatic influences would prevail but Haskell has gone elsewhere. Those values are worth reconsidering
I agree with this, except the "gone elsewhere" part. Haskell has far more pragmatic influences today than it did ten years ago when I started using it professionally. The change is slow, but it is in the right direction.
Yes, Haskell's choices are often overwhelming. Think of Ruby On Rails, where the community has a well-worn playbook and everyone knows the game. Then compare Haskell, which hits designers with an overwhelming menu of choices, and the community still hasn't picked the winners.
Glancing at r/haskell, people often ask for help in choosing web frameworks, SQL libraries, effect systems and monad transformers, regex libraries, text datatypes, lens packages and so on. Simple Haskell and Boring Haskell tried eliminating those problems but the community ignored their pleas, occasionally dismissing the idea with frank negativity.
> what abandonware and a tiny community have to do with cognitive load -- I agree they're bad, I just don't see the connection to cognitive load.)
Our due diligence on 3rd party libraries investigates how active a library is, which includes github submission frequency, online discussions, blog posts, security fix responsiveness, courseware, etc. Activity runs from extremely high (like pytorch) to graveyard code written long ago by graduate students and researchers. Between those endpoints, the big middle is often murky and requires lots of contingency analysis, given that we're delivering real systems to clients and they must stay absolutely safe and happy. All that analysis is brain-deadening, non-productive cognitive load.
Obviously none of this is unique to Haskell, but it's fair to say that other platforms provide more standardized design conventions, and for my needs, a quicker path to success.
The 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.
Sure. 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.
The 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.
Great 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)
> 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.
If 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.
The 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.
> 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.
I don't think it's a winning strategy, but I'm in no position to make hiring or programming-language decisions, and I don't have the market insight that would be required to start my own company.
> 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?
I 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.
Sure, 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:
So 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.
> 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 :)
Regarding 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."
It'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.
And 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.
I 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.
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.
% 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.
I 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 }
.
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.