Pleasures of Programming
Mar 20, 2026
I have a confession to make, I still haven’t used Claude Code. I have, at times, used LLM Chatbots to help answer questions and even generate some code for me, in a similar way that I would have copy/pasted from stack exchange before LLMs. But I haven’t set up Claude yet.
I programme as a hobby. It started from a discovery of linux, and the desire to learn some bash to make some small quality of life improvements. Then, I started learning clojure, and have mainly been using that since. I don’t programme for work, I don’t know enough about programming to make a living off it even if I wanted to.
In this sense, why would I use Claude Code? From the outside, it looks like a productivity tool. Why would a hobbiest carpenter need the latest industrial wood-cutting machine? (especially one that would make most of his activity obsolete)
From the outside, it looks like it takes away the joy of programming, which is what I am interested in when just coding for fun. What do I mean by the ‘joy’ of programming though?
What I don’t enjoy about programming is “boiler-plate”. That’s probably why I like clojure, it is pretty minimal in that sense. I don’t enjoy those parts of programming where you are writing out “word-for-word”, in a sense, the logic of the thing you are trying to produce, when you are describing a system through code. Or, maybe you are doing something like just configuring a system by using someone elses library or framework and setting certain parameters in the functions. Some people might like this, but I don’t. When I hear about vibe coding it sounds similar to this, but using natural language to describe the system instead of code.
For some, the joy of programming might be in the product, the output, and for these people LLMs must be great.
For me, there are two main types of pleasures in programming:
- Fine-tuning
- Discovering re-usable/elegant logic
I don’t have much experience with the first, but what I mean by it is when you, though trial and error, come to understand the underlying behaviour or characteristics of a system or technology you are working with, and through that understanding you are able to write some code that results in huge performance gains. The performant, well-functioning, well-reasoned code at the end is very satisfying, but the main pleasure here is the learning experience. Learning more intimately about some kind of network behaviour, or data compression structure, or memory management approaches in your cpu, etc. I am sure that LLMs can arrive at similarly elegant algorithms for dealing with these more low-level phenomena, but you lose out on this process of discovery.
The second source of pleasure is harder to explain. It is the experience I occasionally have where I have written some kind of function or collection of functions in one area of the programme, and then I have moved on to work on another area or add some kind of additional feature. Then, when I am thinking of how to write the feature I realise that I can just re-use the same function I already wrote in a different way. At the time I was writing those functions, I didn’t envision that they could be used in different ways, but there was something about the logic or process that is ‘generalisable’ and composable.
It’s not as if I ‘designed’ the outcome in that way, but it is more a reflection on how programming is also about working with a language; and you are discovering ways that, just like natural language, ambiguities or the polysemy built into languages can be harnessed for interesting outcomes. So, if the first case above the joy comes from discovering more about a technology, the second comes from discovering more about a language.
As an example, clojure like many other lisps, has a certain way of treating ‘falseness’. Generally, nil, or the simple absence of something, can be used to indicate that something is ‘false’. Through this logic in the language, interesting patterns can emerge that wouldn’t emerge as easily in other languages. For example, I was recently working through 99 Lisp Problems. The third problem is to write a function that returns the nth element of a list. The first, naive, attempt was as follows (in common lisp):
;; Contrary to how the problem is set out, I am presuming 'zero-indexed' lists.
(defun nth-element (list nth)
(if (= 0 nth)
(car list)
(nth-element (cdr list) (1- nth))))
(nth-element '(a b c d e) 3)
D
In the above, we are just stepping through the list, whilst also decrementing the ’nth’, so the function gets called recursively like this:
(nth-element '(a b c d e) 3)
(nth-element '(b c d e) 2)
(nth-element '(c d e) 1)
(nth-element '(d e) 0) ;; 'nth' is at the target value, 0, so just return the first element => 'D'
D
There are many ways to write this, but this was just the first one I tried. Of course, there is an obvious error - there is no ‘checking’ of the arguments, what if the target ’nth’ doesn’t align with the list? For example:
(nth-element '(a b c d e) -1)
(nth-element '(a b c d e) 213)
In the first case the list would recur infinitely, in the second it would be a very inefficient way to find that there is no 213th element.
I started trying to explicitly ‘catch’ these cases:
(defun nth-element (list nth)
(if (or (< nth (length list))
(> nth (length list)))
(format nil "Some error message....")
(if (= 0 nth)
(car list)
(nth-element (cdr list) (1- nth)))))
(nth-element '(a b c d e) -1)
Some error message....
And now the function has become very verbose. I was starting to get into the territory of writing ‘boiler-plate’ or trying to enumerate every possible condition. However, I quickly realised that all that was necessary for this to work in lisp-land (where nil can be interpreted by other functions in predicable ways) was to embrace when
(defun element-at (list nth)
(when (< -1 nth (length list))
(if (= 0 nth)
(car list)
(element-at (cdr list) (1- nth)))))
(element-at '(a b c d e) -1)
NIL
Now, the function simply checks if ’nth’ argument is greater than zero and less than the length of the ’list’ argument, and if it is within that range is proceeds, otherwise it returns nil. In more plain language: when the ’nth’ argument is within the bounds of the list, go ahead and look for the ’nth’ element of the list, otherwise return nothing.
Some people may prefer more explicit type-checking or error handling here, but I think there is something very satisfying about a function that either returns what you asked for or returns ’nothing’ if what you asked for can’t be found.
Anyway, the point here is that there is a kind of joy in finding ways to express logic that are aligned with the language itself, and that you have to ‘discover’ through taking the language on its own terms. If you were to over-specify the function here, to align with the needs of a particular architecture or domain, you are also constraining those unique ’language’ properties and removing the possibilities of future novel ways to re-use/re-compose the function.
When you are using an LLM, does it even matter what language you are using?
These kinds of joys that come from discovering the nuances of how particular technologies are implemented, or how languages choose to express logic, are arguably lost when using an LLM to write the code. From a ‘systems’ or ‘architecture’ perspective, perhaps there is a joy in using the LLM to realise a certain vision or design, but I have always been someone who enjoyed ‘systems’ that emerge through the interactions of different parts or modules (unix-style), rather than conform to some pre-defined vision.
Addendum
Okay, I also had to try asking ChatGPT the same problem to see what it would come up with out of interest. Turns out it comes up with something pretty similar to what I had, but probably even better. So, I suppose you can also argue that LLMs have the capability to improve both productivity and quality.
ChatGPT version:
(defun nth-element (lst n)
(cond
((null lst) nil) ; list ended before reaching n
((zerop n) (car lst)) ; found the nth element
(t (nth-element (cdr lst) (1- n)))))
;; Loop style
(defun nth-element (lst n)
(loop for item in lst
for i from 0
when (= i n) return item
finally (return nil)))
(nth-element '(a b c d e) 3)
D