Clojure is still not for geniuses
You -- yes, you, dummy -- could be productive in Clojure today.
The inspiration for the article I wrote last week entitled Clojure is not for geniuses was inspired by Tommy Hall’s talk at Euroclojure 2014, wherein he made an offhand joke about preferring Clojure for its minimal syntax, as he possesses a small brain (both his blog and his head suggest this assertion is false). I had intended to bring this up with the original article, but got sidetracked talking about immutable things and never got back around to it. Here I’d like to address that, along with some discussion that arose in various forums after the first article.
This article is not about how Clojure is great. I mean, it is, but I’d like to focus on the points that make it an accessible and practical language, without any faffing about with homoiconicity and macros and DSLs and all that.
Today’s all about illustrating some more ways in which I believe our good comrade Clojure can appeal to and empower the proletariat in ways that certain other languages can’t, through the power of simplicity.
Why simple?
This is a callback to the title: simple software, for the definition of simple that I’m using now, is intrinsically easier to read, write, and understand. Probably the most famous presentation in the Clojureverse is Simple made Easy by the man himself, and I think it deserves its reputation.
So, for the remainder of this essay, I’ll be meaning “simple” as an intriniscally good property and a goal unto itself.
A Clojure Syntax Primer
(Don’t worry, I’ll keep it short. You can read all about how great lisp syntax is somewhere else).
You can complain about parens all you want, but you can’t say that Lisp syntax isn’t simple by any definition:
(function argument argument)
This covers a solid 90% of Clojure code. To effectively write it, you’d need a few more things:
; a comment
'a-symbol
:a-keyword
"a string"
[a vector]
{:a map}
Some other lisps, by the way, take umbrage at the dispensing of parentheses for brackets and braces for vectors and maps, but Clojure decided that this diversion from tradition was worthwhile. Beyond this, reader macros exist for some other characters, which you’d need to know or look up to read clojure code that uses them.
Of course, the set of non-alphanumeric characters used in a language is a poor heuristic for measuring simplicity. The simplicity comes from regularity – computations are delineated by parens. There is no need to memorize things like operator binding priority. Order of execution is simply a non-issue, and things like “statement vs. expression” or “reserved keywords” simply disappear.
Dynamic Typing
Just kidding, I’m not touching that one. Any comments regarding this subject will be summarily deleted.
Referential Translucency
“Referential transparency” means that functions accept values and return values, and don’t adjust or depend on anything outside of these values. This is a great property for a function to have, and it’s a pity more software doesn’t adhere to it. It makes functions very easy to test, and very easy to trace.
The problem with functional programming is that about 90% of everything that typical software does can be represented simply, logically, and naturally using referentially-transparent functions, and the other 10% makes a complete hash of it.
For example, a function of zero arguments that generates a random number violates this rule; a pure random number function would accept the same seed each time. In Haskell:
import System.Random
main = do
g <- newStdGen
print $ take 10 (randoms g :: [Double])
Note that g
must be passed to randoms
. I might be accused of
being ungenerous in including the main declaration, but newStdGen
actually must be called within the context of an IO monad, which ultimately
derives from main
.
Clojure is designed to strongly prefer pure functions, but it’s not above bending the almighty rules of lambda calculus if it makes something easier to understand. Here’s the built-in random number function in Clojure:
(rand)
Of course, if you had a good reason, you could always pass in a random generator all the way from your main function too.
A few other obscure things that pure functions can complicate in this way:
- Database access
- Reading/writing files
- Accessing external APIs
- Caching
So if Clojure lets you just do whatever, how can it be considered a “functional” language? That depends strongly on how you define “functional”, but the answer I’m getting at is, because Clojure makes it easy to do the right thing and harder to do the wrong one.
For example, if I had a simple loop like this:
def fact(n):
total = 1
for ii in range(1, n + 1):
total *= ii
return total
I could write it in Clojure like this using atom
, mutable
total and all:
(defn fact [n]
(let [total (atom 1)]
(doseq [ii (range 1 (inc n))]
(swap! total (fn [t] (* t ii))))
@total))
But notice that to use a mutable value added boilerplate? It’s simply not the natural way to solve this problem. (For completionists, here’s one accepted way:
(defn fact [n]
(reduce * 1 (range 1 (inc n))))
-- or in Haskell
let fact n = foldl (*) 1 [1..n]
)
Here’s an important point when contrasting against Haskell directly: Clojure lets you do things the “wrong” way. This is important for accessibility, because it allows anyone getting started with Clojure to bumble through their first project. This is subjective, but I think to say that a greater % of working programmers could produce a more functional Clojure product in a weekend than a Haskell one is a relatively uncontentious statement – the proportion of people who succeed in building their first project in a given language is a strong indicator of the number of people who will end up building a second or third or fourth.
Concurrent Programming fit for the masses
Concurrency is becoming a more important tool for developers, for obvious reasons (multicore processors are apparently a thing). But in most “mainstream” languages it’s still not a straightforward order.
When you work with immutable data and pure functions (or impure ones that interact with transactional storage), running procedures concurrently is a no-brainer. In current popular dynamic languages such as Python, PHP, or Ruby, concurrency is fiddly and often hamstrung by the interpreter. On the other hand, in Java, concurrency requires substantial class boilerplate and can run into trouble with mutable variables.
In Clojure, concurrency is natural and – here’s that word again – simple:
(let [result (future do-a-thing)]
; ... later
@result)
Probably due to factors like this, there are an incredible number of libraries with interesting concurrency constructs written in and for Clojure.
So why isn’t everyone using it?
That’s the big question. Clojure has grown immensely in popularity, but it’s still not a household name. There are a lot of reasons for that – mainstreamn languages have been around a lot longer, naturally, and obviously people are still producing software in them.
I think the fundamental value proposition of Clojure is that it is a simple language that produces robust software. There are simpler languages, and there are more robust ones, but I don’t know of any language that is both simpler and more robust than Clojure. It’s an accessible functional language at a time when functional languages are on the up, and I think that will serve it well.