Clojurescript with Leaflet
Last week I talked about some testing I have been doing with PureScript & Leaflet. In that post I mentioned I had also been doing some testing with ClojureScript.
If you are new to ClojureScript, it is a compiler for Clojure to JavaScript. Clojure is a Lisp that works on the JVM and even the .NET CLR (although I have never used the CLR). To learn more about Clojure, I’d recommend The Joy of Clojure and Web Development with Clojure which are two books I have found really helpful. I’m still a Clojure novice myself, but I have grown to really enjoy working with it.
There are some differences between Clojure and ClojureScript, but it’s not a stretch to go from one to the other. Here is a handy ClojureScript cheatsheet and you can even try ClojureScript in the browser.
All in one
Although not a requirement, I like to use Clojure to run my local development server. I use Ring-Server with Compojure for routing. I won’t go into detail on these, but I also use hiccup as my server-side HTML renderer. If you’re familiar with other templating languages, it works pretty much the same way.
Even if you don’t know Clojure, you can probably make out the DOM structure fairly easily just by looking at the code above.
On to the ClojureScript
ClojureScript provides methods to interact with native JavaScript that may take a little getting used to. There is the aget method that can return the property from something and the this-as macro which allows you to assign the this value to something. Macros are a way to really power-up Clojure/ClojureScript and do neat things like DSLs. Accessing the properties of a JavaScript object may look odd as well. Say you wanted to access the esri extension of Leaflet. You would normally just do L.esri. In ClojureScript this looks like (.-esri L). If there were no dash in there, ClojureScript would treat esri as a method instead of a property. I like to set up a module in ClojureScript with some of these properties already defined, just to make it easier to put an app together. This is what the sample looks like.
You can think of def as defining a variable, whereas defn defines a function. Every Clojure/ClojureScript module gets a namesapce, hence the ns in the above code. In ClojureScript this translates to a module that can be used by the Google Closure compiler, which is what ClojureScript uses when it compiles to JavaScript. There are a lot of benefits to this that don’t become readily apparent until you use some of the advanced optimization features. There are some other really neat optimizations ClojureScript does with JavaScript. I found this JavaScript Jabber podcast to have insights into some of these optimizations.
So with that little helper module built, I can move on to putting things together.
I create a helper function to load the style for the features loaded to the map. The clj->js macro translates a Clojure data structure to a JavaScript one. So (clj->js { :name “Chuck” } translates this to a JavaScript object of { name: “Chuck” }.
You can also see some syntax using let. This creates a local variable for the function. So (let [d (js/Date)]) would bind a new Date object to the local variable d, but it would only be good in that function. You can see it used in the code above to return the map object from a couple of methods so that it could be passed around.
With helper functions defined, you can bring it all together in the init method, defined as (defn ^:export init [_] …). This method is what is made available in the compiled JavaScript to start the application and was initialized in the layout.clj we saw earlier. The rush is in the easy way to put these functions together to easily compose the app in a single line. ((comp bindPopup (partial fLayer opts) basemap loadMap)). comp is a method that allows you to compose multiple functions together. So in this example the result of loadMap is passed into the basemap method and so on. Clojure doesn’t curry its methods, but you can use the partial method to build a curried function like (partial flayer ops) that won’t complete until the the map is passed to it.
That looks like a lot of trouble
Clojure and ClojureScript may seem a bit jarring at first, but I have grown very fond of using parentheses and working with it over the past couple of years, even if it is only has a hobby language at this point. I’m not saying it’s for everyone, but some of the underlying optimizations of ClojureScript were deemed so popular they’ve even made their way into their own JavaScript libraries so that anyone could use them in their apps. I’ve deployed a couple of simple viewers for internal stuff using this and I’m working on getting good enough to build more involved apps that can really take advantage of what ClojureScript has to offer. I’m sure the above code could probably be improved, but I’ll tweak it as I learn more.
You can find all the code for this sample on github. I even dabbled with using Mapbox with ClojureScript in this example a while ago. I’m a big fan of giving different languages a try and gaining new perspectives in my development, and I encourage you to do the same as well.