Learning a new language generally requires significant investment of thought and effort, and it is only fair that programmers expect each language they consider learning to justify that investment. Clojure was born out of creator Rich Hickey's desire to avoid many of the complications, both inherent and incidental, of managing state using traditional object-oriented techniques. Thanks to a thoughtful design based in rigorous programming language research, coupled with a fervent look toward practicality, Clojure has blossomed into an important programming language playing an undeniably important role in the current state of the art in language design. On one side of the equation, Clojure utilizes Software Transactional Memory (STM), agents, a clear distinction between identity and value types, arbitrary polymorphism, and functional programming to provide an environment conducive to making sense of state in general, and especially in the face of concurrency. On the other side, Clojure shares a close relationship with the Java Virtual Machine, thus allowing prospective developers to avoid the costs of maintaining yet another infrastructure while leveraging existing libraries.
In the grand timeline of programming language history, Clojure is an infant; but its colloquialisms (loosely translated as "best practices" or idioms) are rooted in 50 years of Lisp, as well as 15 years of Java history. (While drawing on the traditions of Lisp and Java, Clojure in many ways stands as a direct challenge to them for change.) Additionally, the enthusiastic community that has exploded since its introduction has cultivated its own set of unique idioms. The idioms of a language help to define succinct representations of more complicated expressions. Although we will certainly cover idiomatic Clojure code, we will also expand into deeper discussions of the "why" of the language itself.
In this article, we discuss the weaknesses in existing languages that Clojure was designed to address, how it provides strength in those areas, and many of the design decisions Clojure embodies. We also look at some of the ways existing languages have influenced Clojure.
The Clojure Way
Let's start slowly.
Clojure is an opinionated language it doesn't try to cover all paradigms or provide every checklist bullet-point feature. Instead it provides the features needed to solve all kinds of real-world problems the Clojure way. To reap the most benefit from Clojure, you'll want to write your code with the same vision as the language itself. As we walk through the language features, we discuss not just what a feature does, but why it's there and how best to take advantage of it.
But before we get to that, we'll first take a high-level view of some of Clojure's most important philosophical underpinnings. Figure 1 lists some broad goals that Rich Hickey had in mind while designing Clojure and some of the more specific decisions that are built into the language to support these goals.
Figure 1: Broad goals of Clojure showing some of the concepts that underlie the Clojure philosophy and how they intersect.
As the figure illustrates, Clojure's broad goals are formed from a confluence of supporting goals and functionality, which we will touch on in the following subsections.
Simplicity
It's hard to write simple solutions to complex problems. But every experienced programmer has also stumbled on areas where we've made things more complex than necessary, what you might call incidental complexity as opposed to complexity that's essential to the task at hand (see "Out of the Tar Pit" for details on the concept). Clojure strives to let you tackle complex problems involving a wide variety of data requirements, multiple concurrent threads, independently developed libraries, and so on without adding incidental complexity. It also provides tools reducing what at first glance may seem like essential complexity. The resulting set of features may not always seem simple, especially when they're still unfamiliar, but we think you'll come to see how much complexity Clojure helps strip away.
One example of incidental complexity is the tendency of modern object-oriented languages to require that every piece of runnable code be packaged in layers of class definitions, inheritance, and type declarations. Clojure cuts through all this by championing the pure function, which takes a few arguments and produces a return value based solely on those arguments. An enormous amount of Clojure is built from such functions, and most applications can be too, which means that there's less to think about when trying to solve the problem at hand.
Freedom to Focus
Writing code is often a constant struggle against distraction, and every time a language requires you to think about syntax, operator precedence, or inheritance hierarchies, it exacerbates the problem. Clojure tries to stay out of your way by keeping things as simple as possible, not requiring you to go through a compile-and-run cycle to explore an idea, not requiring type declarations, and so on. It also gives you tools to mold the language itself so that the vocabulary and grammar available to you fit as well as possible to your problem domain Clojure is expressive. It packs a punch, allowing you to perform highly complicated tasks succinctly without sacrificing comprehensibility.
One key to delivering this freedom is a commitment to dynamic systems. Almost everything defined in a Clojure program can be redefined, even while the program is running: functions, multimethods, types, type hierarchies, and even Java method implementations. Though redefining things on the fly might be scary on a production system, it opens a world of amazing possibilities in how you think about writing programs. It allows for more experimentation and exploration of unfamiliar APIs, and it adds an element of fun that can sometimes be impeded by more static languages and long compilation cycles.
But Clojure's not just about having fun. The fun is a by-product of giving programmers the power to be more productive than they ever thought imaginable.
Empowerment
Some programming languages have been created primarily to demonstrate some nugget of academia or to explore certain theories of computation. Clojure is not one of these. Rich Hickey has said on numerous occasions that Clojure has value to the degree that it lets you build interesting and useful applications.
To serve this goal, Clojure strives to be practical a tool for getting the job done. If a decision about some design point in Clojure had to weigh the trade-offs between the practical solution and a clever, fancy, or theoretically pure solution, usually the practical solution won out. Clojure could try to shield you from Java by inserting a comprehensive API between the programmer and the libraries, but this could make the use of third-party Java libraries more clumsy. So Clojure went the other way: direct, wrapper-free, compiles-to-the-same-bytecode access to Java classes and methods. Clojure strings are Java strings; Clojure function calls are Java method calls it's simple, direct, and practical.
The decision to use the Java Virtual Machine (JVM) itself is a clear example of this practicality. The JVM has some technical weaknesses such as startup time, memory usage, and lack of tail-call optimization (TCO). But it's also an amazingly practical platform it's mature, fast, and widely deployed. It supports a variety of hardware and operating systems and has a staggering number of libraries and support tools available, all of which Clojure can take advantage of because of this supremely practical decision.
With direct method calls, proxy
, gen-class
, gen-interface
, reify
, definterface
, deftype
, and defrecord
, Clojure works hard to provide a bevy of interoperability options, all in the name of helping you get your job done. Practicality
is important to Clojure, but many other languages are practical as well. You'll start to see some ways that Clojure really
sets itself apart by looking at how it avoids muddles.
Clarity
When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle they call this a tweetle beetle bottle puddle paddle battle muddle. Dr. Seuss
Consider what might be described as a simple snippet of code in a language like Python:
x = [5] process(x) x[0] = x[0] + 1
After executing this code, what's the value of x
? If you assume process
doesn't change the contents of x
at all, it should be [6]
, right? But how can you make that assumption? Without knowing exactly what process
does, and whatever function it calls does, and so on, you can't be sure at all.
Even if you're sure process
doesn't change the contents of x
, add multithreading and now you have another whole set of concerns. What if some other thread changes x
between the first and third lines? Worse yet, what if something is setting x
at the moment the third line is doing its assignment are you sure your platform guarantees an atomic write to that variable,
or is it possible that the value will be a corrupted mix of multiple writes? We could continue this thought exercise in hopes
of gaining some clarity, but the end result would be the same what you have ends up not being clear at all, but the opposite:
a muddle.
Clojure strives for code clarity by providing tools to ward off several different kinds of muddles. For the one just described, it provides immutable locals and persistent collections, which together eliminate most of the single- and multithreaded issues all at once.
You can find yourself in several other kinds of muddles when the language you're using merges unrelated behavior into a single construct. Clojure fights this by being vigilant about separation of concerns. When things start off separated, it clarifies your thinking and allows you to recombine them only when and to the extent that doing so is useful for a particular problem. Table 1 contrasts common approaches that merge concepts together in some other languages with separations of similar concepts in Clojure.
Conflated | Separated |
Object with mutable fields | Values from identities |
Class acts as namespace for methods | Function namespaces from type namespaces |
Inheritance hierarchy made of classes | Hierarchy of names from data and functions |
Data and methods bound together lexically | Data objects from functions |
Method implementations embedded throughout class inheritance chain | Interface declarations from function implementations |
Table 1: Separation of concerns in Clojure.
It can be hard at times to tease apart these concepts in our own minds, but accomplishing it can bring remarkable clarity and a sense of power and flexibility that's worth the effort. With all these different concepts at your disposal, it's important that the code and data you work with express this variety in a consistent way.
Consistency
Clojure works to provide consistency in two specific ways: consistency of syntax and of data structures.
Consistency of syntax is about the similarity in form between related concepts. One simple but powerful example of this is
the shared syntax of the for
and doseq
macros. They don't do the same thing for
returns a lazy seq
, whereas doseq
is for generating side effects but both support the same mini-language of nested iteration, destructuring, and :when
and :while
guards. The similarities stand out when comparing the following examples:
(for [x [:a :b], y (range 5) :when (odd? y)] [x y]) ;=> ([:a 1] [:a 3] [:b 1] [:b 3]) (doseq [x [:a :b], y (range 5) :when (odd? y)] (prn x y)) ; :a 1 ; :a 3 ; :b 1 ; :b 3 ;=> nil
The value of this similarity is having to learn only one basic syntax for both situations, as well as the ease with which you can convert any particular usage of one form to the other if that becomes necessary.
Likewise, the consistency of data structures is the deliberate design of all of Clojure's persistent collection types to provide interfaces as similar to each other as possible, as well as to make them as broadly useful as possible. This is actually an extension of the classic Lisp "code is data" philosophy. Clojure data structures aren't used just for holding large amounts of application data, but also to hold the expression elements of the application itself. They're used to describe destructuring forms and to provide named options to various built-in functions. Where other object-oriented languages might encourage applications to define multiple incompatible classes to hold different kinds of application data, Clojure encourages the use of compatible map-like objects.
The benefit of this is that the same set of functions designed to work with Clojure data structures can be applied to all
these contexts: large data stores, application code, and application data objects. You can use into
to build any of these types, seq
to get a lazy seq
to walk through them, filter
to select elements of any of them that satisfy a particular predicate, and so on. Once you've grown accustomed to having
the richness of all these functions available everywhere, dealing with a Java or C++ application's Person
or Address
class will feel constraining.
Simplicity, freedom to focus, empowerment, consistency, and clarity.
Nearly every element of the Clojure programming language is designed to promote these goals. When writing Clojure code, if you keep in mind the desire to maximize simplicity, empowerment, and the freedom to focus on the real problem at hand, we think you'll find Clojure provides you the tools you need to succeed.
Why A(nother) Lisp?
By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems. Alfred North Whitehead
Go to any open source project hosting site and perform a search for the term "Lisp interpreter." You'll likely get a cyclopean mountain of results from this seemingly innocuous term. The fact of the matter is that the history of computer science is littered with the abandoned husks of Lisp implementations. Well-intentioned Lisps have come and gone and been ridiculed along the way, and still tomorrow the search results will have grown almost without bounds. Bearing in mind this legacy of brutality, why would anyone want to base their brand-new programming language on the Lisp model?
Beauty
Lisp has attracted some of the brightest minds in the history of computer science. But an argument from authority is insufficient,
so you shouldn't judge Lisp on this alone. The real value in the Lisp family of languages can be directly observed through
the activity of using it to write applications. The Lisp style is one of expressivity and empowerment, and in many cases outright
beauty. Joy awaits the Lisp neophyte. The original Lisp language as defined by John McCarthy in his earth-shattering essay
"Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I" defined the whole language in terms of only seven functions and two special forms: atom
, car
, cdr
, cond
, cons
, eq
, quote
, lambda
, and label
.
Through the composition of those nine forms, McCarthy was able to describe the whole of computation in a way that takes your breath away. Computer programmers are perpetually in search of beauty, and more often than not, this beauty presents itself in the form of simplicity. Seven functions and two special forms. It doesn't get more beautiful than that.