During the past year, I've been reading about and building projects with a programming language called, Clojure. For those who aren't familiar, Clojure is a modern-day LISP that runs on the JVM. Yes, it's one of these parenthesis hogs...
(defn greet (println "hello!"))
Not to worry, this isn't some new programming fad that just started. In fact, I'd say I was quite late on the Clojure hype. LISP itself dates way back to the 1950s, and Clojure was released right around 2007 when I was still in middle school. Over the past decade, the Clojure community has steadily been growing in size, and has since managed to pull me in. I want to explain why I've been "pulled in". This isn't an introduction tutorial on Clojure. There are much better resources for that, and I'll post a few below if you're interested in learning the language from the start. What I want to do instead, is explain some reasons that I've stuck with the language for as long as I have. According to the the 10x rule of business, your product has to be at least 10x better than your competitors for customers to switch. I don't know how well this applies to a programming language, but I think something similar can be said. This is the first of a series of posts where I will showcase the parts of Clojure that have convinced me it is quote "10x" better.
Side note: I will have some code examples throughout the post, and it may be beneficial for you to run them on your own. Open a Clojure repl here.
Code is Data
A piece of the Clojure puzzle that intrigued me early on was the idea that "code is data". Meaning, Clojure code is written as a data structure of the Clojure language. If that's confusing to you, don't worry, I was lost when I first heard it too. This post is aimed to help explain the "code is data" concept and why it's useful.
Again, this isn't a tutorial on Clojure, but I'll need to teach you some basic syntax, if you're new to the language.
Invoking a function:
(<function> <arg1> <arg2> <arg3> ... <argn>)
A set of open/closing parens tells Clojure, "I want to invoke a function!" The first item inside the parens is your function name, and all remaining items are passed as arguments to your function.
When you see this:
(dosomething 1 2 3)
dosomething(1, 2, 3);
Here are some more examples to help put your brain at ease.
(+ 1 2 3 4);=> 10(str "Hello, " "world!");=> "Hello, world"(list 1 "cat" 2 "dog");=> (1 "cat" 2 "dog")
Now, let's get back to the "code is data" stuff.
I'm actually going to start with one of the examples you just saw above. In particular, we're going to look at the
list function again.
(list 1 2 3 4);=> (1 2 3 4)
You may have noticed that the output of this function (i.e the list it returns) looks eerily similar to the syntax we used to invoke functions. This isn't a conincidence. As I mentioned at the beginning, Clojure is a dialect of LISP (LISt Processor) which means that the syntax of the language is entirely built of lists.
Take another look at the "code is data" definition from earlier
"Clojure code is written as a data structure of the Clojure language."
(+ 1 2 3 4) is valid Clojure code, and it's represented as a list.
(+ 1 2 3 4) and
(1 2 3 4) are both lists, the former just so happens to be a valid function invocation.
This begs the question, "how does Clojure know to treat
(+ 1 2 3 4) as a function invocation and
(1 2 3 4) as a data structure containing numbers?" It actually doesn't know. We have to denote that ourselves as the programmer. If you try typing
(1 2 3 4) directly into a Clojure REPL you'll see the error first hand. In the next section we'll discuss how we can prevent our list literals from being evaluated as Clojure code.
The Almighty Quote
Up until now, we've only created lists by using the
list function, and have yet to write a list literal. I've been stalling. Yes,
(1 2 3 4), is how you textually represent a list, but as we saw before, this list will be processed by the Clojure compiler as a function call. We don't want that. We want a list to hold data for us like we would in any other programming language. Ahem, introducing...the quote.
'(1 2 3);=> (1 2 3)
When you preface a list in Clojure with a single quote character
', you're telling the Clojure compiler, "don't evaluate this as a function call." By quoting our list the compiler simply leaves the list as-is. It's not treated as Clojure code it's treat as a plain ol' list that's storing some data. With quotes, the Clojure syntax and list data structures are able to work together in harmony.
This leads us to a more interesting question. What happens if we wrap valid Clojure code in quotes?
'(+ 10 1);=> (+ 10 1)
We've quoted a clojure expression (often called forms), and it's returned a list. Although this output is logically consistent with what we saw before, it's worth taking a second look at it. It's a list, but it's also valid Clojure syntax. It's just data, but it's also code.
Think about it this way. If we can write a Clojure program that manipulates a list of data, we can just as easily write code that manipulates code.
Aside from the unique "meta-ness" of it all, there is value in the relationship between data and code in Clojure. In fact, it allows for one of Clojure's most impressive superpowers, macros.
A macro is kind of like a function. It takes input and returns some output. The difference is that while functions input and output values (numbers, strings, etc.), macros take code as input and return code as output. The output code of a macro will be generated when your program is evaluated and then run alongside the rest of your program. Macros are like a secret backdoor into the Clojure compilation process. Anything from adding syntactic sugar to extending the Clojure compiler itself is possible with macros - making Clojure a programmable programming language.
In Clojure, there is a very unique relationship between data and code. The syntax of Clojure is written using a data structure of the Clojure language - lists! This means that our Clojure programs can manipulate valid Clojure code just as easily as they can manipulate a regular list of numbers or strings. In succinct terms, code is data.
If you're new to Clojure, I'm sure parts of this were a little confusing, and that's ok. My goal was not to teach the basics of Clojure or give you a perfect understanding of what macros are. With this series, I just want to highlight some unique features of Clojure that will make you curious. If you want to do more digging, I've posted some resources below to get you started 🙂
Resources to get started with Clojure: