A day with Clojure
It has been a hectic month for me at Logic Soft. I’m working on quite a few design related activities for the upcoming book fairs and other internal office work. In doing all of this, most of my programming related activities have been put on hold.
This was ok at first, but then it kind of started getting to me. I really enjoy design but then I find it less satisfying than writing programs.
Yesterday morning, as I was performing my early morning browse-reddit-for-cool- stuff routine I noticed an inviting link that said “Part 6 of building a Compojure address book: Deployment” on /r/clojure. From my previous experience, I knew Compojure had something to do with web apps so I opened this up and checked if all the other parts from 1 through 5 were there for completeness sake. After a quick browse, I decided that I’d spend my day with this.
I’ve dabbled on and off with Clojure but i’ve never really had the time to go through with any of them. This seemed small enough for me to complete within a day and substantial enough for me to learn some nice Clojure concepts. Clojure to me has been really fascinating for a variety of reasons ranging from It is a lisp to Immutability to Rich Hickey’s talks; but that is a post for another day.
This post details some of my learnings, both good and bad in and about Clojure as I followed this tutorial. You can find my interpretation of the tutorial code here.
1. Hiccup
Part 2 of the tutorial introduced HTML templating using Hiccup and I was marvelling at the simplicity of representing HTML within clojure. I have heard about the ease of writing DSLs with Clojure but this just set things into perspective. Here is the first piece of Hiccup code I encountered:
(defn common-layout [& body]
(html5
[:head
[:title "Address Book"]
(include-css "/css/address-book.css")]
[:body
[:h1#content-title "Address Book"]
body]))
There is a lot of readability and a great level of expressiveness in this code. Jarrod uses much complex HTML structures in later parts of his tutorial but everything falls under the same level of readability and expressiveness as this one.
2. Tests
I find writing tests for a web app non trivial since we have to mock the request but a combination of the Midje framework and ring request mocking provides a really nice platform to work with to write tests. This is one of the more complex tests that we wrote in the project to test the delete functionality:
(fact "Test delete"
(query/insert-contact<! {:name "A"
:phone "1"
:email "[email protected]"})
(query/insert-contact<! {:name "B"
:phone "2"
:email "[email protected]"})
(count (query/all-contacts)) => 2
(let [response (app (mock/request :post "/delete/1"))]
(:status response) => 302
(count (query/all-contacts)) => 1
(first (query/all-contacts)) => {:id 2
:name "B"
:phone "2"
:email "[email protected]"}))))
The way that the entire pain of mocking a request is taken care of by (app (mock/request :post "/delete/1"))
and nothing more was something I found, calm. It is really non trivial to read this code and understand what this test is trying to do as opposed to having lines and lines of monkey patching to mock a request.
3. Atoms
In part 3 of the tutorial, Jarrod uses Atoms to manage the list of contacts in memory. I was under the impression that Atoms in Clojure are the same as in Scheme but I was mistaken. Atoms are a way of managing shared, synchronous and independent state in Clojure. The documentation on Atoms in Clojure is a very nice read to understand how and when to use Atoms
4. Thread last macro
The one thing that I’ve heard most lisp-ers talk about is the power of macros. Today I was witness to the sheer power of the concept of macros and in particular, Clojure’s Thread last macro. In part 3 of the tutorial, Jarrod writes a function next-id
to calculate the next id to be used when a new contact is being created and the code is like this:
(defn next-id []
(->>
@contacts
(map :id)
(apply max)
(+ 1)))
When I understood what this does, I just put my hand on my mouth and smiled like a kid getting a new toy. This, was beautiful. Let me try and explain this.
The thread last macro takes the result of an expression and inserts it in as the last item in the next form. This means that the result of @contacts
is inserted as the last item to (map :id)
. Further, the result of that is inserted as the last item to (apply max)
and so on. The expression, overall would be something like this:
(+ 1 (apply max (map :id @contacts)))
Using the thread last macro makes this much easier to read. It feels like reading a set of steps and that boosts the understandability of the code. Here is the output at each step:
@contacts
dereferences thecontacts
atom and gives us[{:id 1 :name "A"} {:id 2 :name "B"}]
. This output is inserted as the last item to the map functionThe
map
function takes input from Step 1 and becomes like so(map :id [{:id 1 :name "A"} {:id 2 :name "B"}])
. This will apply the:id
function on to each of the map in the list and return an array of the id’s in the map VIZ[1 2]
. This output is inserted as the last item to the apply functionThe
apply
function applies a function to a given list of arguments. In this case we apply themax
function to the input from step 2. So(apply max [1 2])
becomes(max 1 2)
and gives us3
which is inserted as the last item to the + function.The
+
function adds 1 to the result in step 3 and returns it
The way all this was expressed in such a short and sweet manner using the Thread last macro really left me in awe.
5. Yesql
Yesql is a library for interacting with databases with Clojure in a rather different way. From what I remember with my work in other languages, I’ve written SQL queries that I make from within the program in 2 ways:
- Using ORMs like SQLAlchemy
- Plain vanilla SQL inside language specific strings
Yesql brings a new thought process to the table where we keep our sql as sql within .sql
files. You then use the defqueries
function within Yesql.core
to define queries based on the queries in the .sql
files.
To illustrate, Part 4 of the tutorial talks about replacing the @contacts
atom with an actual db. Consider the query to select the list of all contacts from the db. It would be:
-- name: all-contacts
-- Returns a lits of all the contacts
SELECT name, phone, email from CONTACTS
once you use Yesql.core
’s defqueries
on a file containing the above sql, it will generate a all-contacts
function that we can use for firing the query on the db. Here is how we use the defqueries
method to define a query out of this sql file:
(defqueries "path/to/sql/file.sql" ...)
What this does, is it looks through file.sql
and within the module, creates methods taking the name from the comment above the query (-- name: all-contacts
). Further, this module can be imported from other places and used like this:
(:require path.to.module :as query)
(print (query/all-contacts))
This would fire the corresponding SQL from the file on the DB.
I found this a rather interesting coming from a world where queries were written in rather primitive formats.
5. General readability of Clojure and Lisps
I’ve always been a giant fan of Lisps. I find the S-Expression notation very readable and far more understandable after a point when compared to the infix notation.
This was my first experiment with writing proper Clojure. I.e. Clojure that actually makes an application run and I should say that my notion of increased readability with Lisps has been strengthened. There were very few parts on the tutorial that I got stuck because of me not understanding the code.
This I think is the power that Lisps come with. Once you get a sense of S-Expressions, they become a breeze to read.
6. Compile time
One of the things that I felt that would be an instant discouragement to newcomers is the amount of time it takes to run a Clojure project. At the end of Part 1 of the tutorial, we had a very simple GET
and POST
based web server and some tests behind it. Running lein midje :autotest
for the tests took a noticable 10s at the least and running lein ring server
to bring up the server took more time than that.
This I felt was an instant discouragement because it breaks the flow in your head and doesn’t allow for easy experimentation and rapid learning.
All in all, I had a great day learning Clojure and I really have to thank Jarrod for his amazing 5 part tutorial which is what made all this happen. Please feel free to point out any mistakes I might have made in this post, for I am still learning my ways around this beautiful language and its concepts.