Micro languages in Clojure via macros and let.
While working on a couple of web projects using moustache I’ve been slowly building up some abstractions for dealing with SQL for data durability. The abstractions are built on top of the clojure.contrib.sql library which works great. However I was interested in being able to express my queries in a more “Clojuresque” way. The library that immediately popped to mind was the ClojureQL project. Alas there were two issues with ClojureQL. First and foremost, it seems abandoned or at least stagnant at the moment, hopefully that will change as it has lots of potential. The second issue is that the abstraction it provides seems backwards. Here’s an example:
In Clojure the collection generally comes last. So perhaps something like this, would be more idiomatic:
Let’s see if we can build a tiny SQL abstraction layer to provide the above interface. Let’s agree before we begin that one of our goals should be to minimize namespace pollution, especially considering we’ll be redefining some clojure.core functions. Users of the library (aka me) shouldn’t have to prefix every function or resort to the use of (:use).
We don’t want this:
A thought occurred to me the other day for working around this issue. Perhaps we can locally rebind symbols using let inside a macro to give us the interface we’d like:
Much nicer in my opinion. That thought in hand, let’s get down to work. Starting with collect macro we have:
(The sqlize function simply transforms :table into “table” and :table/col into “table.col”.)
Here’s our first use of let to rebind a symbol. as is bound to an anonymous function but only within the context of collect. Now a quick test of the new collect macro:
So now we can generate select statements, next up is filtering those “collections”. Here’s the filter macro:
Again, we use let to bind symbols local to only the macro. Time to test filtering:
It works! Here’s our (in)complete SQL DSL:
That’s it for now, in the next article we’ll fix our nesting issue, and implement bound variables in the generated SQL.
If you want to play around, all the code is available on Github.