The Joy of Clojure

Introduction

We don't yet have a strong psychology and sociology of programming languages. One day we will, and we will be able to retroactively make sense of why Clojure is so powerful.

6usJE0L.jpg

Figure 1: The soul! A computer. Solar punk Clojure. The inside of a computer. Rubics cube colored gems are flying around. Lighting.

Paul Graham has suggested The Taste Test for understanding the power of a language. A programming language is a tool for thinking. Ultimately, its power comes from letting you think thoughts and express them. With sensibility (hypersensitivity), you can tell which language feels more restrictive than another.

If all languages were the same, we would be programming assembly. But we are not, because we live in a world where languages have varying degrees of power.

Here I try to describe a few things that set Clojure apart, even from other Lisps.

What interactivity brings

With Lisp, you can test each piece of code as you write it, and know it works before you move on to the next piece. You don't have to keep all the things that could go wrong in your head at once. Small experiments are cheap, so you make lots of them.

Here is a simple example:

(file-exists-p "~/.clojure/")

Are you getting the file path right? When I write Lisp, I know the answer while I am writing the program. I just eval whatever form I have to build the file path. Because of structural editing, I can wrap whatever form in (file-exists-p) quickly and experiment right then and there.

Doing little experiments is quick enough so it can mesh with your thinking. I have the freedom and agility to just do and move. We get the same power in the small during development and in the large while diagnosing a problem in production. 1

A slightly bigger example. Say an HTTP handler returns the wrong thing. In a Clojure REPL I don't add print statements and restart. I capture the request:

(defn handler [req]
  (def req req) ;; inline def: keep the last request around
  (process req))

Then I hit the endpoint once from the browser, and from now on req is a plain map in my REPL. I can call (process req) as often as I like, change process, re-evaluate it, call it again. The bug is now a data structure on my desk, not a ghost in the server.

Philosophy of simplicity

Clojure first clicked for me via Rich Hickey talks. Keep things simple. Let data be data. Then you can just manipulate the essence of the stuff and be done. 2 , 3

He establishes the reasons for Clojure and the opinions on how to build things. And this permeates the culture.

The combination of data-orientedness and functional programming while making real-world pragmatic tradeoffs, lends itself to a complete4, robust, no-bullshit style.

Strong community

When I use a library I can go to the Slack channel and usually the maintainers help me out. It happened to me more than once that I made a bug report and somebody fixed it within a few days. Everybody is excited about technology and wants to help you out. It ties in with the joy of Clojure as well. You get happy, friendly people. This is power. Humans are more effective when they are happy and friendly.

Not to mention Borkdude, who maintains clj-kondo, babashka, nbb and more, and with whom I got to work on a few of them.

Non-breaking versioning

It is an explicit value (again from Rich Hickey's philosophy) to not make breaking changes.5 This is one of the things where once you have it, boy, you don't want to ever go back.

After acquiring this taste, getting broken because I bump a version makes me think Why are you doing this to me?.

The aesthetics of data orientedness

The core library is a huge lever because we lift everything that can be into the space of data structures, then manipulate those data structures.

Config input, REST API response, database queries, and responses. It is all the same kind of stuff once it is in our system.

;; a config file, an API response, a database result: all just maps
(->> users
     (filter :active)
     (group-by :team)
     (update-vals count))

The same four functions work whether users came from EDN, JSON or Datomic.

I think of this as a lift and manipulate kind of lever. Unix has this with program input output text. Once you are in the realm of textual input and output you can leverage shell commands to manipulate. Emacs has the same thing with text in buffers. If you can express your domain as text in the buffer you can give it to the user for manipulation, and you can leverage the core library for manipulating text in buffers.

This data-orientedness is something I can expect from libraries and other people's code. The dominating amount of stuff will always be pure functions acting on plain data structures.

Freedom and joy

The workflow is an utter joy, with the REPL at the center of it. I start a program once and it grows together with my ideas. There is something about a program that runs for days and you don't restart it that I cannot explain but it does something to your mind. The closest analogy is a kind of friendship with the program.

  • inline def some of your data, craft your code forms.
  • Functional programming (and The Value of Values)
    • able to fabricate data
    • able to get the time right with atomic successions
    • able to easily keep records of the past

I get a real kind of safety. The kind where I can sleep at night, because I can see that my program does what it should. You get this from there being few enough ideas in the program that you can hold the relevant pieces in your head.

A program that is small enough to be self-evident is dramatically more likely to work. 6

It helps that everything is accessible from Emacs, which is the other ancient and powerful tool that feels like an extension of my mind.

Because of this focus, solidity and ease of development, I am more joyful and creative when programming with Clojure.

It allows me to focus on the actual problems. So I can spend time on getting the user experience right etc.

Three years later

I wrote this post in early 2023, before I wrote Clojure for a living. Now I do, on a large Clojure and ClojureScript codebase with Datomic. What held up?

  • The REPL scales. On a big system the REPL matters more, not less. The running app is the thing I talk to: reload one namespace, call one function with real data, look at the result.
  • Data at the boundaries. Malli schemas are just data, so one schema can live in a .cljc file and check the same shapes in the browser and on the server.
  • Accrete, don't mutate. Datomic records facts and never overwrites them. Bookkeeping, as it should be.
  • The REPL is now shared. My coding agent evaluates forms in the same running REPL I use. It does not guess what a function returns; it asks the program. Interactivity turned out to be exactly what an LLM needs, too.

What I would add today: the joy is not only aesthetic. It is speed. Small experiments, many of them, all day.

Open questions:

  • Is the "friendship with the program" something that can be taught, or only experienced?
  • With an agent in the loop, does the REPL become the main interface to the program, more than the source files?

Summary

So here is a tool that lets me think the right thoughts and express them. It lets me move with ease where I want to be.

From Lisp, I get the interactivity and the tradition of FP and simplicity. When I want to make a small experiment with the code, I can just do a small experiment with the code.

Beyond that, there is a solid and dependable community, the data-orientedness and the leverage of targeting big platforms.

I can model information systems and can make the program just do what it needs to do, and do nothing more.

Clojure just delivers.

The silent competence of the computer.

(Marvin Minsky)

Note

There is a book of the same title, by Michael Fogus and Chris Houser. This is not accidental. The power of Clojure also lies in the joy it brings. An alternative title for this post could be Joy is power, in analogy to Paul Graham's Succinctness is Power.

More on Clojure: Clojure and Interactive Programming.

Footnotes:

2

I believe I see hints of the same craftsmanship of getting-things-done ideas in other places: Brian Will, Object-Oriented Programming is Embarrassing, whose counterexamples are very straightforward and complete, and Chris Keathley, SOLID code isn't flexible.

3

Rich Hickey's greatest hits. The starting point. Get your mind expanded by simplicity and clear thinking.

4

Complete in the sense that you make a piece of code that gets the job done, instead of scattering logic around the place.

5

Rich Hickey, Spec-ulation, Clojure/conj 2016.


Generated by Claude on Benjamin Schwerdtner's behalf.

6

Stuart Halloway has similar thoughts, Radical Simplicity. Other people like Dan Friedman also made this relatively explicit. I think at this point it is obvious that simplicity is crucial for building good software. The question is what is simplicity and how to do it?.

Date: 2023-01-26 Thu 09:48

Email: Benjamin.Schwerdtner@gmail.com

About
Contact