Cognitive Loads in Programming

https://rpeszek.github.io/posts/2022-08-30-code-cognitiveload.html

I was thinking about this topic for a long time. I would be very interested in your feedback.
This post has high level technical discussion and some notes about psychology, but it mostly came from thinking about code as something that comes with cognitive effort and that this effort should be kept under control.

It is a high level rant that includes quite a bit of FP and Haskell.
Thank you for taking a look.

5 Likes

It looks like you’re looking for something more denotative than what’s currently available - you’re not alone

1 Like

Very interesting post on a very interesting topic.

Cognitive load of working with code is rarely considered. We ask “How long will it take?” (in fibonacci numbers, of course), we do not ask “How will it impact the overall complexity?”. IMO, thinking about cognitive load can expose project issues that typically go unnoticed.

I totally agree.

Reading the article, I had difficulties identifying your main points. What is it you want me to understand? Or: What is your most important claim or proposition?

I remember that I learned about a very new, different kind of cognitive load when I switched to Haskell. When reading Haskell code, every time there is a new concept I have to slow down and my forehead wrinkles. When I want to use a library that makes use of concepts that I don’t understand, the problem intensifies.

However, before programming in Haskell, the cognitive load wasn’t something unheard of. I remember quite well the difficulties that I had everytime when I got back to work on a somewhat older project. I had to read a lof of (my old) code to recover the confidence that I needed to make a change or add a new feature.

Being an expert in your own code can feel deceptively satisfying.

It took me a while to learn that there are ways to reduce the cognitive load that my own code causes. And as you correctly point out that has a lot to do with complexity and programming paradigms.


I like to add that Haskell-specific cognitive load can be preferable. Ideally, when I learn advanced Haskell concepts, I am familiarizing myself with concepts of general importance and by mastering more of those, I am becoming a better programmer.

Compare that to the cognitive load that a couple of GOTO statements can cause (as an example of my own, bad code). I am not learning anything profound when I become expert of some bad code.


I have feedback regarding formatting:

Sometimes, not always, after a paragraph there is an empty line. I highly recommend consistent spacing after paragraphs to reduce noise.

2 Likes

Denotative vs imperative belongs, at least, in “There is much more to it” section.
Thanks!

Thank you for your observations.

I have not tried to be very prescriptive about solutions (other than show some common sense approaches that reduce complexity). The main point I tried to convey is that thinking about code in terms of cognitive load psychology is something that could be very useful, and that is not being done today (EDIT please do not interpret this as “no progress is being made in making programming simpler”, only as observation that cognitive effort is not considered explicitly and that this could be very useful).
As such, this is a collection of loose notes about various cognitive aspects of code rather than a claim about an approach to coding. (If we were thinking about cognitive aspects we would have not done this or that + what are the learning opportunities that code can offer).

On the styling note, this is pandoc output from a markdown, I am not explicitly controlling paragraph spacing, however I am trying to avoid line breaks in certain places and prefer to start a new sentence on a new line. But I will take your comment into consideration.

Thank you!

1 Like

Just not true [the “not being done” part]. I see you’ve only “27 years of professional programming work”; so you’ve missed out on all the big developments in Programming Language Theory and practice. In particular you’ve missed the change from programming close to the iron (Assembler languages); then not-very High Level Languages (like C or COBOL or early Basics); and now OOP or Functional, with a high level of abstraction – precisely to reduce the cognitive load of marshalling all those bytes around memory.

Simplicity is overrated, of course. And Joel has much sage advice – including the dangers of thinking that starting again/throwing away crufty code will make life simpler.

2 Likes

I personally never witnessed a consideration to change a requirement because it is going to result in significant long term cognitive effort.

Complexity is not considered that much by mainstream languages and popular framework maintainers either, otherwise we would not have so many gotchas and “this is a feature not a bug”. My post links to examples.

I may have phrased it better, I do not want to imply that no progress is made to simplify programming.
So I will try to edit my reply above.

You comment about my lack of understanding of PLT is uncalled for. IMO, PLT is not applied enough by the mainstream.

Then we must have had careers in entirely different branches of the industry (or possibly entirely different universes ;-). Balancing software complexity/longer-term ‘software maintenance debt’ against meeting requirements is a conversation in every design meeting. I’ve never been closely involved in configurations or languages to maintain them – perhaps that explains our different experiences.

I’ve worked mostly for/with package developers. Clients buy our software precisely because they have necessarily complex requirements (tax regulations, manufacturing/supply chains), and because they can’t afford to maintain in-house developers to keep up. If you don’t comply with tax regulations or supply-chain product-safety protocols, you don’t have a business.

It may well be that policy/regulations-makers don’t ‘apply PLT’ enough/don’t consider implementation complexity.

Note that coding is only about 10% of the effort of getting software to market and keeping it there. If your language or tools are sub-optimal, that has less impact than failing to keep up with tax regulations. And anyway any language is just fancy FORTRAN.

Software vendors are under continual commercial pressure to compete by bringing out ‘features’ to put them ahead/or at least keep up with their peers. The big shake-out in the mid-size ERP market over the past ~decade shows what happens when vendors can’t keep up. Some of the products that are now sunsetted, I would say are better coded and cleaner software design than those that have persisted.

Hmm? Do you have evidence how language committees make decisions about evolution? You’ve put this post on a Haskell forum (not a mainstream language – and perhaps you’d have got different points of view on YAML/Dhall forums): re GHC there’s discussions every week challenging whether a proposal’s benefits outweigh its added complexity – both for GHC internals and cognitive load for those using Haskell.

2 Likes

I have added a list of explicit objectives at the beginning of my post clear. Thank you for pointing out that these were not clear.

It does appear that we have different experiences, it is good to know that considerations like these are done in some companies.

I used “mainstream language” to mean a large popular massively used PL like Java, JS, Python, TS,
this comment is not targeting Haskell.
I posted here to solicit feedback because I included a bunch of references to Haskell.

Thank you for reading my post and for your feedback. I am sorry you found nothing useful in it.

I’ll drop some separate replies to the post as I go along.


In the simple vs easy section I believe you missed talking about complicated vs complex.

You can have non-complex code that is quite difficult to both create and consume, but in some sense simple (dense, well thought through). An example is “tying the knot”. It isn’t really complex and you can reason about it properly on a high level, but following the evaluation flow is quite difficult. This even goes for Haskell in general in some sense. Reasoning about evaluation is complicated, but not complex.

Complicated is not so much about working memory overload, but classifying/identifying a situation or property.

So the question is, how does “simple” semantically fit in here?

1 Like

Aww I didn’t say “nothing useful”. I said we had different experiences – probably because of being in different sectors of the industry.

I’ve had plenty of those sort of navel-gazing discussions with programmers – who seem to think the business of a business is to produce elegant software. Even for a software house (whose business is to produce software), they still have to sell/implement the software to a business whose business is ultimately not producing software; for them the user-interface is where they measure elegance/cognitive load – if they think about that at all.

For a vendor to explain it’s now hard to adapt the package to comply with changed requirements because of some short-term/inelegant/internal cognitively complex design decision several years ago, is a good way for the vendor to get shown the door.

So better to approach (at least commercial) software development expecting it will be cognitively challenging and inelegant and sub-optimal. And be thankful that’s what makes such a high bar for entry, and pushes up the charge-out rates. (We can all tell stories of businesses that tried to save costs by outsourcing dev/maintenance/support to allegedly low-cost countries.)

2 Likes

I think a valid point.
This is somewhat orthogonal to cognitive load theory which is more interested in learning and instruction design but obviously very relevant.

On evaluation front, I do consider this to be high intrinsic difficulty, especially in languages that separate evaluation from execution and allow rewire rules, etc. Normalization in LC is not trivial, e.g. to have strong normalization you need a very simple language like simply typed LC. I am saying even forget lazy evaluation things are not obvious (and undecidable territory).
I think we are getting into imperative vs denotative here as well, denotative is by definition less “prescriptive” so arguing an instruction flow in something that, say, uses Applicative will be tough.

So evaluation in Haskell is, to me, not simple, I would call it complex (hard to consume).
Thankfully for us, this tends to be not that important in practice (at least for typical apps).

I do see your point that about complicated being a bit different.
I think your point is also that “simple” needs a context. Simple to do what? Simple to consume depends on the topic at hand (e.g. performance optimization vs verifying correctness are different topics).

Thanks! This was insightful.

1 Like

System F is much more complicated (about the same as Haskell minus unrestricted recursion) and still strongly normalizing. Also, some dependently typed languages like Coq and Agda are very complicated but still (probably) strongly normalizing. At least a dependently typed core calculus, the calculus of constructions, has been proven to be strongly normalizing.

…much like a total language - perhaps the restriction on general recursion can be (mostly) alleviated in similar fashion:

In programming languages, totality also means something like:

let blah = blah 
in blah

would be considered an error. For more, read David Turner’s Total Functional Programming.

1 Like

off-topic: has anyone advanced on Turner’s proposal and implemented an elementary total functional programming language?

You could try asking Conor McBride (the author of “Turing-Completeness Totally Free”) - see http://strictlypositive.org for contact details.

Yes. And lazy evaluation is usually simple to consume! I’d disagree that it’s not simple. It’s the reason we can compose functions so easily.

However, in some cases, it breaks down. And those cases reveal that it’s indeed quite complicated.

So consumption isn’t one dimensional either. It relates to circumstance, intuition and ability to predict.

I think intuition is the main key here. We build intuition about concepts to make sense of our code without looking at core. But this is always an approximation. This issue is unrelated to complexity, IMO.

I think in psychology you might say complexity relates to working memory and “complicated” relates to linear intelligence.

1 Like

Well Dhall is total, all functions are total and terminating.
I would assume crypto contract space will have some contract languages like that but I do not work in that space.

These are probably not what you are looking for?
Proof assistants can reason about totality.

I agree, my definitions “simple / complex consumption” are not considering the topic being consumed,
I assumes an implicit topic. That topic is typically making sure that code works (correctness)

but as it is, I have oversimplified the situation.