Simplicity and Power in Action

All of the distinctive features in Clojure are there to provide simplicity, power, or both. Some of these features include concise and expressive programs, the power of Lisp updated with a modern syntax, an immutable-first approach to state and concurrency, and an embrace of the JVM host and its ecosystem. Let’s look at a few examples that demonstrate these high-level features.

Clojure Is Elegant

Clojure is high signal, low noise. As a result, Clojure programs are short programs. Short programs are cheaper to build, cheaper to deploy, and cheaper to maintain. This is particularly true when the programs are concise rather than merely terse. As an example, consider the following Java code, from Apache Commons:[5]

data/snippets/isBlank.java
 public​ ​class​ StringUtils {
 public​ ​static​ ​boolean​ isBlank(​final​ CharSequence str) {
 int​ strLen;
 if​ (str == ​null​ || (strLen = str.length()) == 0) {
 return​ ​true​;
  }
 for​ (​int​ i = 0; i < strLen; i++) {
 if​ (Character.isWhitespace(str.charAt(i)) == ​false​) {
 return​ ​false​;
  }
  }
 return​ ​true​;
  }
 }

The isBlank method checks to see whether a string is blank: either empty or consisting of only whitespace. Here is a similar implementation in Clojure:

src/examples/introduction.clj
 (​defn​ blank? [str]
  (every? #(Character/isWhitespace %) str))

The Clojure version is shorter. But even more important, it’s simpler: it has no variables, no mutable state, and no branches. This is possible thanks to higher-order functions. A higher-order function is a function that takes functions as arguments and/or returns functions as results. The every? function takes a function and a collection as its arguments and returns true if that function returns true for every item in the collection. Note that this definition also works correctly for special cases like null and the empty string without requiring explicit checks.

Because the Clojure version has no branches, it’s easier to read and test. These benefits are magnified in larger programs. Also, while the code is concise, it’s still readable. In fact, the Clojure program reads like a definition of blank: a string is blank if every character in it is whitespace. This is much better than the Commons method, which hides the definition of blank behind the implementation detail of loops and if statements.

As another example, consider defining a trivial Person class in Java:

data/snippets/Person.java
 public​ ​class​ Person {
 private​ String firstName;
 private​ String lastName;
 
 public​ Person(String firstName, String lastName) {
 this​.firstName = firstName;
 this​.lastName = lastName;
  }
 
 public​ String getFirstName() {
 return​ firstName;
  }
 
 public​ ​void​ setFirstName(String firstName) {
 this​.firstName = firstName;
  }
 
 public​ String getLastName() {
 return​ lastName;
  }
 
 public​ ​void​ setLastName(String lastName) {
 this​.lastName = lastName;
  }
 }

In Clojure, you’d define Person with a single line:

 (defrecord Person [first-name last-name])

and work with the record like so:

 (​def​ foo (->Person ​"Aaron"​ ​"Bedra"​))
 -> #​'user/foo
 (:first-name foo)
 -> Aaron

defrecord and related functions are covered in Protocols.

Other than being an order of magnitude shorter, the Clojure approach differs in that a Clojure Person is immutable. Immutable data structures are inherently thread safe, and update capabilities can be layered when using Clojure’s references, agents, and atoms, which are covered in Chapter 6, State and Concurrency. Because records are immutable, Clojure also provides correct implementations of hashCode and equals automatically.

Clojure has a lot of elegance baked in, but if you find something missing, you can add it yourself, thanks to the power of Lisp.

Clojure Is Lisp Reloaded

Clojure is a Lisp. Lisps have a tiny language core, almost no syntax, and a powerful macro facility. With these features, you can bend Lisp to meet your design, instead of the other way around. Clojure takes a new approach to Lisp by keeping the essential ideas while embracing a set of syntax enhancements that make Clojure friendlier to non-Lisp programmers.

Consider the following snippet of Java code:

 public​ ​class​ Person {
 private​ String firstName;
 public​ String getFirstName() {
 // continues

In this code, getFirstName is a method. Methods are polymorphic and give the programmer control over meaning, but the interpretation of every other word in the example is fixed by the language. Sometimes you really need to change what these words mean. So, for example, you might do the following:

These kinds of needs are commonplace. In most languages, you would have to petition the language implementer to add the kinds of features mentioned here. In Clojure, you can add your own language features with macros (Chapter 8, Macros). Clojure itself is built out of macros such as defrecord:

 (defrecord name [arg1 arg2 arg3])

If you need different semantics, write your own macro. If you want a variant of records with strong typing and configurable nil-checking for all fields, you can create your own defrecord macro, to be used like this:

 (defrecord name [Type :arg1 Type :arg2 Type :arg3]
  :allow-nils false)

This ability to reprogram the language from within the language is the unique advantage of Lisp. You will see facets of this idea described in various ways:

The downside of Lisp’s simple, regular syntax, at least for beginners, is the prevalence of parentheses and lists as the core datatype. Clojure offers a combination of features that make Lisp more approachable:

Many Clojure programmers will be new to Lisp, and they’ve probably heard bad things about all those parentheses. Clojure keeps the parentheses (and the power of Lisp!) but improves on traditional Lisp syntax in several ways:

Clojure is an excellent Lisp, for both Lisp experts and Lisp beginners.

Clojure Is a Functional Language

Clojure is a functional language but not a pure functional language like Haskell. Functional languages have the following properties:

For many tasks, functional programs are easier to understand, less error prone, and much easier to reuse. For example, the following short program searches a database of compositions for every composer who has written a composition named “Requiem”:

 (​for​ [c compositions :when (= (:name c) ​"Requiem"​)] (:composer c))
 -> (​"W. A. Mozart"​ ​"Giuseppe Verdi"​)

The name for does not introduce a loop but a list comprehension. Read the earlier code as, “For each c in compositions, where the name of c is "Requiem", yield the composer of c.” List comprehension is covered more fully in Transforming Sequences.

This example has four desirable properties:

Contrast functional programs with imperative programs, where explicit statements alter program state. Most object-oriented programs are written in an imperative style and have none of the advantages listed here; they are unnecessarily complex, not thread safe, not parallelizable, and difficult to generalize. (For a head-to-head comparison of functional and imperative styles, skip forward to Where’s My for Loop?.)

People have known about the advantages of functional languages for a while now. And yet, pure functional languages like Haskell have not taken over the world, because developers find that not everything fits easily into the pure functional view.

There are four reasons that Clojure can attract more interest now than functional languages have in the past:

Clojure’s approach to changing state enables concurrency without explicit locking and complements Clojure’s functional core.

Clojure Simplifies Concurrent Programming

Clojure’s support for functional programming makes it easy to write thread-safe code. Since immutable data structures cannot ever change, there’s no danger of data corruption based on another thread’s activity.

However, Clojure’s support for concurrency goes beyond just functional programming. When you need references to mutable data, Clojure protects them via software transactional memory (STM). STM is a higher-level approach to thread safety than the locking mechanisms that Java provides. Rather than create fragile, error-prone locking strategies, you can protect shared state with transactions. This is much more productive, because many programmers have a good understanding of transactions based on experience with databases.

For example, the following code creates a working, thread-safe, in-memory database of accounts:

 (​def​ accounts (ref #{}))
 (defrecord Account [id balance])

The ref function creates a transactionally protected reference to the current state of the database. Updating is trivial. This code adds a new account to the database:

 (dosync
  (alter accounts conj (->Account ​"CLJ"​ 1000.00)))

The dosync causes the update to accounts to execute inside a transaction. This guarantees thread safety, and it’s easier to use than locking. With transactions, you never have to worry about which objects to lock or in what order. The transactional approach will also perform better under some common usage scenarios, because readers will never block, for example.

Although the example here is trivial, the technique is general, and it works on real-world problems. See Chapter 6, State and Concurrency for more on concurrency and STM in Clojure.

Clojure Embraces the Java Virtual Machine

Clojure gives you clean, simple, direct access to Java. You can call any Java API directly:

 (System/getProperties)
 -> {java.runtime.name=Java(TM) SE Runtime Environment
 ... many more ...

Clojure adds a lot of syntactic sugar for calling Java. We won’t get into the details here (see Calling Java), but notice that in the following code, the Clojure version has fewer parentheses than the Java version:

 // Java
 "hello"​.getClass().getProtectionDomain()
 ; Clojure
 (.. ​"hello"​ getClass getProtectionDomain)

Clojure provides simple functions for implementing Java interfaces and subclassing Java classes. Also, all Clojure functions implement Callable and Runnable. This makes it trivial to pass the following anonymous function to the constructor for a Java Thread.

 (.start (new Thread (​fn​ [] (println ​"Hello"​ (Thread/currentThread)))))
 -> Hello #object[java.lang.Thread 0x2057ff1f Thread[Thread-0,5,main]]

The funny output here is Clojure’s way of printing a Java instance. java.lang.Thread is the class name of the instance, 0x2057ff1f is the hash code of the instance, and Thread[Thread-0,5,main] is the instance’s toString representation.

(Note that in the preceding example, the new thread will run to completion, but its output may interleave in some strange way with the REPL prompt. This is not a problem with Clojure but simply the result of having more than one thread writing to an output stream.)

Because the Java invocation syntax in Clojure is clean and simple, it’s idiomatic to use Java directly, rather than to hide Java behind Lispy wrappers.

Now that you’ve seen a few of the reasons to use Clojure, it’s time to start writing some code.