I have been using Clojure as my primary programming language at work for over two years now (and I absolutely love it!). I still run into a lot of developer friends or co-workers who fall in one of the two groups:
- Folks who have never heard of Clojure or Lisps in general.
- Folks who have heard of Clojure, but dismissed it as Yet Another Lisp.
In this blog post, I hope to talk to group #2 about what makes Clojure special. But I will start with a brief background for the benefit of group #1.
Unless you have been living under a rock, I assume you would have heard of Functional Programming (FP). It has been gaining momentum in recent times, especially with popular front-end frameworks like React, Redux adopting that paradigm. Even the poster-child of OOP, Java has started supporting FP from Java8 onwards. Modern day services need to be able to scale globally – to serve a global audience through the internet, making concurrency a necessity. Thats where FP shines. Lisps are a class of programming languages, dating back to 1958, built on top of Lambda Calculus, that favor the functional paradigm. Clojure is one of the more recent lisps, created in 2007 by Rich Hickey.
What makes Clojure stand out from other Lisps?
1. Persistent Immutable Data Structures
Clojure has a rich set of data structures. What most people don’t realize is how bleeding edge these are in terms of language/data-structure design. These are a culmination of building on top of and improving upon decades of progress in the field.
Most programmers who have had to face/fix unanticipated production bugs in their careers know about the dangers of mutability. Java developers have tried to cope up with this using measures such as sprinkling
final keywords all over their codebases.
Clojure solves the problem at a higher level of abstraction by making all data-structures immutable by default. If you are new to this concept, you might be wondering how anyone can get anything done if everything is immutable. The key to understanding how this works is to realize the distinction between value and identity, concepts which are usually conflated in OOP/imperative world. In simpler terms, whenever you need to change something, you create a new version of it which is unrelated to the original version from a usage perspective.
If you think about how you might implement immutability, a simple solution might be to copy over the whole data-structure each time it is modified. But you will soon realize how inefficient the scheme is. This is where all the advanced data-structures (such as a Hash array mapped trie or HAMT) that Clojure employs under-the-hood come into play. Historically, you used to have to choose between immutability and performance. With Clojure, you can get both!
As mentioned above, with immutability, you create different versions of the same data-structure over time. Clojure implements immutable data-structures in a way that you can grab any of these previous or current version of the data and further modify them to create newer versions without any issues. This property is called persistence.
Clojure compiles to Java bytecode and can run on the JVM. I cannot stress how much of a game-changer this was for my team. In general, this makes it much easier to start using Clojure in a corporate setting for the following reasons:
- You can sneak in Clojure just like a library into your existing Java code base.
- You or the Clojure community does not have to re-invent the wheel by re-creating every useful library that already exists in Java.
The inability to do these two have been a significant limiting factor in the adoption of other lisps in the past. The second point is huge. Not only can you take advantage of all the libraries in JVM, your Clojure code will work with your company tooling/frameworks built for the JVM seamlessly. The interoperability is really good. You can even expose Java methods and interfaces to your Clojure code so that others can’t tell the difference (see gen-classes).
3. Clojure.spec (Bonus)
Clojure is a dynamic programming language, like most other lisps. One of the key practical implications of that is that you won’t have static type checking for your code. While static type checking does help you catch some errors at compile time occasionally, it has two major limitations:
- The programmer does not get to choose what/where to do static typing. Everything is typed. This amounts to a lot of verbosity and unnecessary noise in the code.
- The type system is fairly limited in its expressibility. You can only check the data-types provided by the language designers.
Unless you experience it first-hand, it is hard to imagine a world where you get a stronger verification capability than that of static typing, but without these two limitations. Clojure spec provides us exactly this. While Clojure spec is not aimed to be a type system, it is much more than that. The best part is that you get Spec out of the box with Clojure, not as a separate library.
To illustrate the point of expressibility, think of the primitive data-types in your favorite statically typed language (say Java). You have your
String data-types. What if in your domain/program, you wish to care about positive and negative numbers with the same level of distinction. You cannot in an easy/straightforward way without creating new classes and objects. This gets trickier if your type definitions are more complex or involves dynamism.
With Clojure spec, you can create new types using arbitrary logic predicates (such as
int?, pos? etc.). Here is an example creating a new spec called
pos-int which describes a positive integer.
(s/def ::pos-int (s/and int? pos?))
You can choose to spec your important data (primarily interfaces) and validate that you are receiving/outputting data that meets the spec easily at runtime. Spec also supports generative testing by auto-generating test data based on the specification. Rather than you calling your function with arbitrary dummy data in your unit/integration tests, you spec its input/output and use that to auto-generate 100s of test inputs to run the function against and programmatically validate that the output meets the spec.
There are many other reasons to love Clojure. Here is a recent article from Uncle Bob (of CleanCode fame) on why he loves Clojure – link. However, the above ones are the key reasons why I think Clojure will succeed where the other Lisps failed.
I hope you will give Clojure a serious consideration for your next (hobby or work) project. While you are here, may I also recommend catching up on Rich Hickey’s (creator of Clojure) Greatest Hit talks – link. These will definitely make you a better programmer, even if you end up never using Clojure.
“every time I watch one of his talks I feel like someone has gone in and organized my brain”https://github.com/tallesl/Rich-Hickey-fanclub
Also, a shout-out to Spacemacs as a great editor for Clojure (and everything else).