Back in 2018 I wrote a, somewhat, critical essay on Clojure and how a lack of developer discipline can easily build up in a Clojure codebase to make it hard to grok and hard to change.
I just wanted to revisit this as some time has passed and in the meantime I’ve worked on three more Clojure codebases as well as a Java one.
Although I still feel all the points that I made in the original post are still valid my recent experience has tempered my opinions.
My main critiques could be summarised as:
- Clojure’s dynamic typing & lack of language enforced patterns can make building a mental model of the problem more difficult.
- Clojure’s lack of ubiquitous frameworks also tends to increase the cognitive load on the developers understanding.
Having worked on three more Clojure codebases and another Java codebase I’d like to readdress my opinion on the cognitive load Clojure tends to introduce compared with that introduced by statically typed languages (or at least those not derived from the ML tradition of Hindley–Milner type systems).
Change trumps all
One of the things I noticed when working on these codebases was that the higher the rate of change the easier the cognitive load imposed by Clojure was compared with Java.
Initially I thought this was counter intuitive as I’d expected that the strict types of Java would reduce the amount of mental juggling I had to do to construct a model that needed to change. Although having types (classes) in Java helped me superficially to determine the shape of data in my mental model that initial at-a-glance definition wasn’t that much of a time saver.
Although Java classes show the data encapsulated in a concept;
- unless small they are not that fast to scan and absorb
- they tend to describe one small concept and not the concept in context
- often you’re only interested in one or two fields in the class and not the whole class
- changing one or two fields may subtly change the meaning of the class or conflate multiple meanings of the same class in different contexts
- even with refactoring tools it can be laborious to change a class to include/exclude data
Treating data as a generic data structure (a map [dictionary] or a vector [array]) means within a given context (i.e. within a call chain or a single function) you only have to change/add/remove a field and that only impacts that field and the access path of that field.
With good naming and use of something like Spec to describe the data at the boundaries of the system or components of the system generic data structures may add a few seconds extra to building a mental model but this is easily compensated by minutes or hours of time saved in changing types (classes/interfaces) or introducing new types (classes/interfaces).
Therefore, I think that Clojure’s ‘disadvantages’ as a dynamically typed language that doesn’t enforce a data definition burden on the developer up front are more than out weighed by the advantages of making changes faster and in a more isolated manner.