Are you someone who is thinking of learning Clojure and Functional Programming? I’ve just started and I hope I can help you!
I am Sameeksha — a 2021 graduate working as a Software Engineer in the data platform team at Helpshift. Clojure is the primarily used language at Helpshift. As part of my on-boarding process, I was introduced to Clojure. While learning the language there were a few things that I found were important to be drilled in the mind and especially when you are just starting. So without further ado let's get started.
Brief Introduction to Clojure
Clojure is a functional programming language, everything here revolves around functions. It belongs to the LISP (List Processing) family of languages. Functions can be passed as arguments to other functions and they can be returned as output values. Pretty cool isn't it!
Let's start understanding with a basic example of how we add 2 numbers in Python and print the result?
print(1 + 2)
Now how to add two numbers in Clojure
(+ 1 2)
Notice the syntax, it is (Function argument1 argument2 ).
Everything in Clojure is surrounded by parentheses. Clojure uses the prefix notation.
Also note, here ‘+’ is not a mathematical operator but in fact a Clojure function.
The evaluation scheme in Clojure treats this as a list where the first value is a function and the rest all are the arguments.
Lists are an important and special data structure in Clojure.
The first point of our discussion is why is it so. Let's try to understand this.
'("Clojure" "Python" "Java" )
This is treated as a list because of the presence of a single quote at the beginning. The reason for having this quote is because in LISP the first element will be the function and anything after that is considered as data for that function.
In absence of the quote at the beginning, Clojure will throw an error that It was trying to find a function but instead it found a string.
'(+ 2 3) ;=> (+ 2 3)
Here even though we were trying to perform addition which ideally should have returned us the value 5 it returned (+ 2 3) itself because of the presence of the single quote.
This leads us to the point that in Clojure ‘Code is Data’. We were trying to write the code for addition but because of a presence of a single quote in this case it was treated the same as data.
Now say, we want to use the result of this addition later at some point in time, can I store this in a variable?
You can’t store anything in a variable, in fact, there is no concept of variables. All the core data structures in Clojure are Immutable. In Java, strings are immutable similarly the value 42 is immutable. Clojure has extended this ideology for all its core data structures.
To understand this point of immutability lets consider an example of a tree shown below
Suppose you need to add a new node called ‘s’ as a left child of node ‘f’.
Think for a moment about how is it going to add when I just said that the data structures in Clojure are immutable.
Now look at the image below
A brand new object is created and the new node ‘s’ is present in it.
Clojure does something called ‘Structural sharing’ behind the scenes where it ensures that a minimum amount of copying is done.
To add node ‘s’ its parent ‘f’ was needed similarly node ‘a’ and copy of node ‘c’ had to be made. Rest all the nodes are being shared. The left part of node ‘a’ is something we weren't touching to hence the new object created called ‘tree_2’ will simply point node a’s left to already existing node b.
This ensures immutability.
Let's take our discussion to another interesting fact which is about Truthiness. Assume a use case where you want to be inside the loop until the vector is empty.
(Note: I am taking an example in Python and referring to the sample code in it as vectors only for the sake of simplicity. In Python, although it is called lists.)
In Python or any OOPs languages, you could have simply answered this by having the while condition as follow
v = 
You already know that whenever the vector will be empty the while condition will turn to be false and the loop will break.
In Clojure however, if you first try to run the below two examples in a REPL you'll notice that when the vector is empty in that case as well Clojure evaluates this to be true.
(if (count [1 2 3 4]) :truthy :falsey) ;=> :truthy
(if (count ) :truthy :falsey) ;=> :truthy
Every value except FALSE and NIL is treated as true.
An empty string, vectors of length 0, number 0 are all treated as true.
So what will you do to check if a collection like a vector or a list is empty?
Answer: use seq.
seq returns the sequence view of a collection and returns nil if the collection is empty.
(seq ) ;==> nil
These were the 3 things that I wanted to talk about in this post.
We started our discussion with how Lists are a key data structure in Clojure
Further, we discussed immutability where even if you try to make an update to an existing object Clojure will create a new object itself. This new object however will persist the previous version so only the new update which is needed will be done. Lastly, we learned that every value except nil and false is treated as true.
In the next post, I'll talk about Java interop and also how can we take up cases where we actually need to mutate something in Clojure and how it happens.
Thanks for reading till the end.
(str "Thank you !!")