Over the course of the past year I've largely swapped out Python for Clojure as my language of choice. While I still love Python as a scripting language with a robust ecosystem and fairly excellent tooling, I can't get over Clojure's speed, power, and simplicity.
To quote my friend @blinsay, I have become a "Lisp weenie".
I didn't start out with a high opinion of Lisp. The college I attended had a computer science program, and the first part of the intro sequence was taught in Scheme. The course professor didn't do a great job of imparting the merits of functional languages, let alone of Scheme, and I dropped out of the course after a few weeks with a bad taste in my mouth.
In retrospect, of course, it wasn't the best reason to drop out of the major, but I was young and left the department convinced that Lisp was a dead language that was only in serious use by academics and masochists.
Fast forward to the start of last year, when I was first applying for work as a web developer. I didn't know what Clojure was, nor had I recovered from my negative experience with Scheme. At that point I had several years' experience working with Python as a data scientist, and had tinkered at various points with Go, Java, and Ruby.
In one early phone screen, in an attempt to head off concerns that had arisen a few times about the transition, I found myself having the following conversation:
Me: "Some of the companies I've interviewed with have raised two particular concerns about hiring a data scientist for a web development position. First, there's a concern about the change in the culture and nature of the work. Second, there's a fear that I won't be able to pick up whatever language you're working in. In all honesty I don't think the first is a legitimate concern, and I'm not particularly concerned about the second."
Them: "Well, we're actually a little concerned about the latter."
Me (with a friendly chuckle): "I'm not sure what language your team is writing in, but so long as it's not Lisp I'm sure we'll be fine!"
Them: "Yeah...it's Clojure."
Me (not knowing what Clojure is but sensing something has gone wrong): *strange gurgling sound*
Funnily enough, that conversation didn't end up being enough of a red flag for them not to hire me, and I've since gone on to help bring Clojure in as the primary language at Standard Treasury, where I currently work.
So what is it that I like about Clojure so much?
The truth is that there are quite a few reasons why Clojure floats my boat. I don't consider the following to be either an exhaustive or a surprising list, nor would I expect anybody to switch if they're happy with their current language of choice. For the uninitiated, however, these are the reasons why I love Clojure:
If I had to give a single reason to use Clojure, it would be this: immutability is awesome. It protects you from yourself in ways that aren't immediately obvious at first, but become more apparent over time.
The argument that follows is particularly catered to the world of web development, but remains applicable in other contexts as well.
In short: application state is a source of complexity, and unwarranted complexity is a developer's worst enemy. This is true on the basic level of single-process, single-threaded programs, and becomes increasingly more so as both thread and process count increase.
Coming from a background in mutable languages, some degree of application state just seems like the cost of doing business - after all, how else to track changes to business objects over time? The answer's pretty straightforward, actually - just push state management into your database.
If that sounds like passing the buck, that's because it is, but that's okay - unless you're running a very particular type of application, odds are you're already using your database as an engine of state; you're just also using your application layer. The thing is, the semantics of database transactions for the purpose of state management are well studied, with the result that most production-level database technologies don't risk the sort of bugs that one encounters by trying to handle state in an application, let alone in an application being run in a distributed environment.
Ultimately, pushing state into the database works because most web applications rarely need to hold onto a particular object for very long, and functional languages like Clojure make it easy to pass values around and to track modifications made to them without mutating the underlying data structures.
There are, of course, times when you really do need a mutable object, and Clojure has types for them as well. They're definitely used, but less frequently than one would think.
I have a lot of trouble overstating the value proposition of immutability, particularly when I'm talking to people whose only reference point is a mutable environment. The only thing I can say is that after a few months of writing Clojure code I couldn't help but notice that an entire class of bug had simply vanished from my regular development flow.
A Powerful [n]REPL and Class Loader
I'm a highly interactive developer; consequently, I love a good REPL. When I was living in Python-land, I always had an iPython window open. These days, it's a Clojure REPL, often either attached to a running web server process or directly handling the aforementioned process in the background.
In and of itself, this is a great tool to have - it makes it easy for to me touch various parts of the running application while I'm developing and to debug in a truly interactive fashion. But the goodies don't stop there - I can also connect to an existing REPL over the network through my editor (I use Vim).
This is unbelievably powerful - it allows me to write functions and test them against the running application from a comfortable and optimized development environment. Not only that, but I can also edit existing functions and dynamically reload them into the JVM using the Clojure class loader, all with the push of a few hotkeys. While the JVM's start-up time isn't trivial, the time required to hot-[re]load namespaces or functions into the JVM is quite fast, and I find myself doing it constantly over the course of the day.
Performance and Other JVM Benefits
Being on the JVM has quite a few advantages, but the big ones relate to performance, deployment, and interopability.
When I talk about performance, I want to be explicit that I'm not claiming Clojure to be one of the fastest languages on Earth. It's not. Even so, when you're coming from an environment where most of your work is done in an interpreted language (Python), the speedup one gets from being on the JVM is considerable.
From a deployment perspective, the JVM is tremendously convenient - just compile a JAR and toss it into your Docker image of choice and you're ready to go!
Lastly, and this will come as a surprise to nobody, there's a real value proposition to be found in the interoperability with the rest of the JVM universe. While I personally find it nice to stay within the world of idiomatic Clojure as much as possible, the breadth of the Java ecosystem means that just about anything I might need or want probably already exists as a Java library, and idiomatic wrappers for those libraries are being written on a daily basis.
I'm of the opinion that the Clojure community's thinking around testing will continue to evolve; an opinion reinforced by the existence of multiple popular testing libraries with competing syntax and formatting. That having been said, the core clojure.test works really well, has an elegant and easily comprehensible syntax, and is easy to jump into.
Consider a basic Python test case:
SimpleTestCase(unittest.TestCase): def runTest(self): self.assertEqual(true, true)
Now let's contrast that with the equivalent in Clojure:
(deftest simple-test-case (is (= true true)))
Clojure's functional syntax and treatment of functions as first-class citizens enable incredibly elegant code authorship and are, for lack of a better word, delicious things to have at one's disposal.
Now, one could make the point that both of the above are true of essentially all Lisps (as well as most modern functional languages), and you'd be correct. They're also largely matters of taste. I don't have much to say to either of those points other than to note that writing something like the following makes you feel like a space wizard:
(map (comp (partial g a) f) x)
Ah, macros. The arcane syntax, the dark murmur of the elder gods, the eldritch glow of your machine as it compiles...
Ahem. Where was I?
Ah, yes: macros. Truth be told, I don't have much to say on the subject of macros that Paul Graham hasn't already said, but to summarize: code written by humans is powerful, and code that can dynamically write and re-write itself is infinitely more so.
Of course, this is a capability that isn't unique to Lisps, but only Lisps use an abstract syntax tree that makes doing so exceptionally easy and natural.
After all that, you could be excused for thinking that I didn't have more to say about Clojure, but I do (in particular, on the subjects of concurrency and asynchronous engineering patterns) - I'm just going to save it for another time.
A lot has been written over the decades about Lisp, with the common thread being: "Lisp's day will come."
Recently, as I was reading Stuart Sierra's Clojure Year in Review 2014, I came to the conclusion that day is here now.
I see multiple posts on Clojure on Hacker News every week. Truly massive companies, including Amazon, are hiring Clojure engineers. I'm currently working for a company that's building a bank in Clojure, and we're not the only ones doing so.
To be sure, there are parts of the Clojure ecosystem that are still under-developed - at least, according to my needs and preferences. Even so, were I to be given the chance to choose a new language to write in every day, I wouldn't pick anything else.
If you've yet to give Clojure a try and are curious about how to get started, I'd recommend taking a look at the Leiningen project page, and potentially picking up a copy of Clojure Programming. Good luck!
Discuss this post on Hacker News or on Twitter (@venantius)
Thanks to Keith Ballinger, Bill Cauchois and Harry Wolff (@hswolff) for reading drafts of this post.