Skip to content

Instantly share code, notes, and snippets.

@sundarj
Last active May 3, 2021 19:34
Show Gist options
  • Save sundarj/5aed13fc3699d7a5c5f401159770389d to your computer and use it in GitHub Desktop.
Save sundarj/5aed13fc3699d7a5c5f401159770389d to your computer and use it in GitHub Desktop.

I am not a good programmer.

If I write in the mainstream, imperative, style, the code invariably results in a mire of utterly avoidable complexity, as I try – and naturally fail – to do the computer’s job for it. It is not the computer’s fault: it simply does what I tell it to. However, I am not good at the low-level details; I think at a much higher level of abstraction than the dumb computer is able to comprehend. I cannot reason at the computer’s level. I am not good at massaging that abstract thought into the lower-level (imperative) format that the computer requires.

Imperative languages are optimised for the computer: that is why they have always been fast, and resource-frugal. I am, alas, a human. I do not enjoy nor want to do computer-level thinking, I do not want to have to keep the irrelevant details of the computer in mind – I have my limits, and have been bitten too many times by being forced to pretend those limits do not exist, and try being a computer. Instead, I would like to reserve as much as possible of my mental real-estate so that I may focus on the interesting ideas I am cogitating about, so that I may manipulate, turn it over, disassemble, and reassemble them in my mind – especially when I am away from the computer. Thus, I must avoid constructs and paradigms which impair my ability to think and reason about said ideas: that is my forte, my raison d’etre – it is only the computer which is good at mutating state and manual fiddling. Functional programming is not perfect in this regard (of course), but it is a lot closer to my powerfully abstract, yet fundamentally limited human mind.

I now think in terms of data (facts; values; definitions; that do not change in-place, just as numbers do not: they merely exist), and calculations, or behaviours (that do not change things in-place; value to value, just as lambda calculus does), with that data, and, regrettably, necessary low-level effects (that do change things in-place, just as computers are supposed to do for me) at the edges of the system in order to get the computer to actually do anything (output a message, send a HTTP request). Luckily the bulk of my program can be written in the first two stages (the design and definition of the domain at hand), so I am free to think and abstract and reason as I please: as my human mind is designed to be used. Until, at the edges, I must do what the computer needs (in order to convert my problem into something ‘useful’). Even then, I can use an abstraction that does the low-level work for me, so that I can stay at the abstract level where I am most efficacious, and therefore happy.

From The Joy of Clojure: The ideal technique for writing a complex program is to slice it into multiple problem-specific pieces and then define a language for each problem slice. If we slice the program vertically, the result is a “tower” of languages, layered atop one another. Regardless of how we slice the overall problem, we can use the right language, and the right paradigm, for each subproblem. In Clojure, it’s common practice to start by defining and implementing a low-level language specifically for the levels above. Creating complex software systems is hard, but using this approach, you can build the complicated parts out of smaller, simpler pieces.

Finally – in the words of Matthew Butterick – solving problems is the lowest form of design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment