There's something about Clojure (part 1)
This is not an introduction to Clojure, there’s plenty of excellent ones already. These are my reflections on why I felt in love with Clojure, although it took some time to build that relation, as a reminder for my future no-self, should it become frustrated by it at some point.
Dear future no-self…
…it was only about six years ago that I came across Clojure. At that time I had over 15 years of C++ and embedded Linux systems under my belt, together with a common mix of other languages such as Java, Python, and Go. Although I’ve had a very brief encounter with Scala, I considered functional programming some exot(er)ic field, certainly not something for professional use. How wrong I was! Yet I didn’t know it when I first saw Clojure, just a snippet of code in the company I was working for at that time. I remember the sensation of being almost blind, of not being able to make sense of that weird syntax. I certainly couldn’t see my(no-)self writing that stuff for a living, and yet life can be so ironic.
Parentheses
Clojure is a dialect of Lisp, which is famous for its parentheses. I didn’t know that When I first saw it, so I just thought it was one of those write-once/read-never academic languages. And again, how wrong I was! Let me explain things here as a reminder to you, my future no-self, because I have now seen the light.
Here’s a “traditional” function invocation:
print("Hello world")
Now let’s move the parentheses a bit;
(print "Hello world")
It’s the same thing but Ta-Daa, welcome to the Lisp syntax! Well, it’s a bit more complicated than that, but you get the point.
No blocks delimited by threatening sharp curly braces:
func crunchNumbers(x int, y int) int {
if x <= y {
return x + y
}
return x - y
}
Instead sequences of sinuous, fluffy, round parentheses:
(defn crunch-numbers [x y]
(if (<= x y) (+ x y) (- x y)))
So relaxing!
Immutability
Functional languages are famous because they don’t have side effects, or at least they do their best to discourage them, nudging programmers toward writing “pure” functions: no side effects -> idempotent execution -> parallelise at will -> big win!
The Go function above deliberately creates a new array, without modifying the input one: this is to mimic the same behaviour as Clojure. Otherwise, a simpler version would be:
func SquareArrayMut(a []int) {
for i := 0; i < len(a); i++ {
a[i] = a[i] * a[i]
}
}
a := []int{0, 1, 2, 3, 4}
SquareArrayMut(a)
fmt.Println(a) // prints [0 1 4 9 16]
After calling SquareArrayMut
the array a
has changed, its original values are no more. In a more complex scenario where a resource is shared among threads, that could lead to hard to spot bugs if not properly protected, e.g. with a mutex. But if data structure is immutable, it can be safely shared and each thread can use it at will.
(def a (range 5)) ;; a = (0 1 2 3 4)
(def squared-a (square a)) ;; squared-a = (0 1 4 9 16)
(println a) ;; prints (0 1 2 3 4)
Nothing unexpected. Honest. Beautiful.
Coinciseness
Let’s write a function that squares the numbers of an array.
In some imperative language, e.g. Go:
func SquareArray(a []int) []int {
result := make([]int, len(a))
for i := 0; i < len(a); i++ {
result[i] = a[i] * a[i]
}
return result
}
a := []int{0, 1, 2, 3, 4}
fmt.Println(SquareArray(a)) // [0 1 4 9 16]
In Clojure:
(defn square [input]
(map (fn [i] (* i i)) input))
(square (range 5)) ;; prints (0 1 4 9 16)
One line. No assignments. Simple. Elegant. Beautiful.
Here map
simply applies the function (fn [i] (* i i))
to the items of input
, which is assumed to be a collection, and returns a collection with the result. The multiplication is writted in Polish notation simply because *
is not an operator, but a function (so consistent!). The arguments of a function are wrapped in square brackets because they form a vector
: this also happens in other languages (e.g. argv
in C/C++, or $@
in bash) but in Lisp/Clojure it’s explicit. Because… Code is Data, Data is Code. Oh beauty, oh perfection!
Next time…
I remember the first person who showed Clojure to me saying enthusiastically how it was “such an expressive language”. Back then, I didn’t really understand what he meant…