The first half-decade of my programming career I was obsessed with trying new programming languages. I couldn't read a post on Hacker News without taking the language for a spin and building some small test project with it. I wanted desperately to master a programming language, yet every new language seemed so much better than the last one. After the recommendation of a good friend and an incredible presentation I started using Clojure and I've begun to use it increasingly more. Since then, I've stumbled across and tried some amazing programming langauges, but nothing has managed to move my focus away from Clojure. I've written tons of side projects with it, used it professionally for a year and just released a small app written in it. Clojure grabbed my attention, but unlike many other languages it has continued to do so. Stopping to reflect I noticed, without ever intending to, I have become a Clojure programmer. "Why Clojure?" is not the beginning of a sales pitch, but a question to myself. Why do I enjoy this language so much?
An important and difficult part of software development is creating consistent software. In Mathematics, functions are pure. A pure function is deterministic which means it is consistent by definition. So if we use values and functions, the problem of consistency disappears. Functional programming takes advantage of this and provides tools for working with concrete immutable values. Fortunately for humanity, and unfortunately for software developers, the world doesn't have a set value, it has state. If our software is going to do something useful, it has to interact with the world. So if our program is going to do something useful, it has to have state.
Object oriented programming does a great job of providing ways to manage this state and this is part of why it has become so popular. Complex object models can be used to represent that state inside of our program. However, with these models of state comes their complexity. Objects tie together behaviors(methods) with state. Connections between things grow at an exponential rate, ala the handshake problem. The larger the system becomes the more complex it becomes, and each additional relationships adds exponentially more complexity. Anyone who has worked with sufficiently large software has seen this problem first hand. An increasingly complex system becomes increasingly more difficult to understand. The system becomes increasingly more difficult to modify, fix, or imprvome.
In Clojure data is immutable by default, utilizing the purity of stateless functions to avoid the problems of compleity introduced by state. However, it still acknowledges the benefits and necessity of state by providing well-defined method for handling mutable identities. Providing constructs that make the manipulation of state explicit. As a result, state is something I rarely think about while programming in Clojure, but is readily available when needed. The majority of a Clojure program can be written as functions accepting and returning values. The separation of behavior from state means when working with the logic of a system you do not have to hold the state of that system in your head. When programming a function in Clojure I am only working on the function and the values passed into it. This style of programming took some time to get used to but once I did, it was incredibly freeing. Gone are the days of tracking down bugs caused by some complicated relationship between every changing entities. No more worrying about the effects of simple changes that may ripple through a complicated system.
A Modern Lisp
Clojure belongs to a family of languages called Lisp. Lisp receives a lot of love and hate in the programming community and most of the criticism of Lisp is levied at its syntax. The amount of time we spend reading code as programmers makes syntax an important part of a programming language. Clojure solves some of the problems with traditional Lisp by implementing enough syntactic improvement to make writing and reading Clojure code mode quicker, compared to traditional Lisps.
As a Lisp, Clojure source code is written as a data structure. Traditional Lisps have been designed entirely around the list data structure, providing a very limited syntax for writing code. Clojure has expanded its support for collections, notably: sets, vectors, and maps. Along with providing additional syntax for creating these types. This produces more readable code than previous Lisps, while still providing the property of homoiconicity, which forms the basis of the incredibly powerful macro system in Lisp languages.
Due to Clojure's homoiconicity, or code as data. When writing a macro in Clojure you are writing a function accepting code, and since this code is data, you can treat it the same way you would any other data. This means you have the full power of the language at your disposal. Understanding just how powerful this simple concept is can be difficult without experience. It is often said there is an "aha moment" when one fully understands the power of Lisp macros. For me, that "aha moment" came with the release of the Clojure core library core.async. The core.async library is an implementation of CSP style channels as a core library. Powerful concurrency tools and syntax implemented not as a language feature, but as separate library composed of standard Clojure code. New libraries with features just as powerful are consistently released relying on the power of macros including tools such as pattern matching, logic programming, and a type system. Tools like these along with the macros you write in your own code make writing code that is understandable much simpler.
Simple Made Easy, the greatest talk I have seen given on programming gives insight into the design process of Clojure as a language. "You can write a sophisticated a system with dramatically simpler tools, which means you're going to be focusing on the system, what it's supposed to do, instead of all the gook that falls out of the constructs you're using." This quote from the talk highlights the philosophy of Clojure as a language. Programming is about solving difficult problems. Focusing on tools that make something easier, often make the tradeoff of introducing complexity. This complexity grows and grows until we spend more time solving problems created by the complexity than we spend solving the problem itself. Clojure is designed with simplicity as a goal and the more time I spend programming in Clojure the more I realize the value of simplicity. Each feature in Clojure is added to the power of clojure as a tool, while mantaining this clear focus on simplicity.
During its development Clojure has consisntently taken ideas from the development of languages and tools coming before it. Taking a piecemeal approach by borrowing the good parts and leaving the unnecessary behind. Lisp's hygienic macros, running on the JVM, CSP style channels and the latest Clojure feature core.spec.spec, are all examples of this approach. core.spec is a great example of improving on great ideas of others. It provides a lot of the tools of a type system and is very similar, but is implemented in a way that fits Clojure's simplicity and flexibility as a dynamic language providing validation, error reporting, parsing, instrumentation, documentation, and generative testing. And the language itself is not the only part of Clojure embracing change. The Clojure community itself is frequently tossing out old assumptions about how we should program. Developing libraries with novel and useful approaches to old problems. From templating to UI-rendering Clojure embraces change. Based on a 50 year old language, from the start Clojure has been molded by the tools before, but has shown no sign of clinging to the past.