Since January I’ve been working with Clojure, a functional language. This comes after over 30 years of programming in procedural and object-oriented languages. Make no mistake, it’s one heck of a paradigm shift.
Clojure is a delight, but its use of immutable data has been a hard thing to get my head around. That is, in Clojure, there are no variables as we know them.* Values in Clojure are assigned once, at most. You would think that working with it would be like writing a C program in which every variable is const. Forget for-loops, accumulators, essentially every thing you know about programming and algorithms.
With the whole immutable data thing hanging over my head, It took a big dose of “willing suspension of disbelief” for me to dig into the language. My reaction was probably similar to that of many other people coming from conventional languages: what good could a program possibly be if its data is immutable?
But if you have been exposed to math at all, the only reason you don’t recoil at a typical assignment statement
n = n + 1;
…is intensive indoctrination, since on the face of it the statement is absurd. We know that one is not equal to two, nor is two equal to three, and in general, for all numbers (natural, real, rational, irrational, and even imaginary), the statement n = n + 1 is false.
So how does one use Clojure to calculate deltas, sums, statistics? Or construct strings? Take what I say with a grain of salt — I’m very much a tyro at Clojure and have no knowledge of its implementation. But I think I can see what’s going on, and I thought it might be worthwhile to write this while I have a foot in each camp, before I am entirely assimilated into the cult of functional programming.
- Clojure allows bindings of expressions to “variables”.
- In Clojure, recursion is not evil.
In a procedural language, you might think of variables as areas in memory that you manage. But in Clojure, instead of swapping different values in and out of a location in memory, you usually have a series of expressions on the stack. Clojure has various logic controls that can govern what you leave on the stack, so with some practice you will find that anything you can accomplish with a “do-loop”, you can accomplish with recursion.
So you are not hopelessly crippled by immutable data. But what does this buy you? Well, your management of a program’s variables is simplified and hence less error-prone: you bind a value to a variable and that’s it. Afterwards, you don’t have to worry about when and if to update it.
And consider for a moment what immutable data means for cache coherency…
Time’s up! And that’s right — cache coherency is a non-issue if you have immutable data. Data can’t get stale if it can’t change. If it’s there, it’s good. Resolving cache coherency is one of many reasons that Clojure is such an excellent fit for concurrent programming and distributed systems.**
Working with Clojure and immutable data requires a different mindset than procedural or OO languages. The change in approach is analogous, although different, to the shift in switching between procedural and object-oriented design: you start from different places, use different mechanisms, do things in different ways, but if you write correct code you’ll get correct answers.
Languages are just tools. Some are more suited to particular tasks than others. Clojure’s fortes are handling concurrency and big data, but more on that later.
*Clojure supports references and atoms whose use and behavior is very similar to that of variables in procedural languages. They are used very sparingly.
**I’ll discuss additional reasons that Clojure is a good fit for concurrent programming in subsequent posts.