Skip to content

Auto-reloading code with Clojure

August 14, 2012

Today, I found an awesome little library called ns-tracker, put together by James Reeves. Most of the code was originally written by Stuart Sierra for his Lazytest tool.

ns-tracker provides a function to call for checking if any of your source code has changed and then provides you with the namespaces and their dependencies so that you can plug it straight into clojure’s namespace loading function. It’s brilliant and absolutely changes the way you code, getting a lot closer to Brett Victor’s Inventing on Principle – a worthwhile watch if you haven’t seen it.

Basic usage

(def tracker
  (ns-tracker ["src" "test"]))

(doseq [ns-sym (tracker)]
  (require ns-sym :reload))

This is the quick way of doing a check for code changes – build a tracker with your source code folders and then use it to find modified files and reloading them.

Background tracking

I did some work to make it into robust little worker thread, since I generally want this to keep going even if the mountains fall over (exception made for SIGTERM):

(defn check-namespace-changes [track]
 (try
   (doseq [ns-sym (track)]
     (info "Reloading namespace:" ns-sym)
     (require ns-sym :reload))
   (catch Throwable e (.printStackTrace e)))
   (Thread/sleep 500))

(defn start-nstracker []
 (let [track (tracker/ns-tracker ["src" "checkouts"])]
   (doto
     (Thread.
       #(while true
         (check-namespace-changes track)))
     (.setDaemon true)
     (.start))))

Now I just run (start-nstracker) when my app starts, and I’m done. My work cycle is now edit code, save, see the result, rinse, repeat. I’m using slightly tweaked versions of the code above for the different apps I’m using.

Note here that I have pulled the actual work into a function instead of glomming the lot together – This is so that if I want to tweak the reloading function, I can do that on the fly too.

Lein checkouts for libraries

You’ll also notice that I’m using “checkouts” instead of “tests” – I can quickly make changes to supporting library code and it’ll load that code up too. Leiningen supports having library sources under the checkouts folder, then it will use that instead of the maven dependency. I use symbolic links in there to le me use one central folder per library.

I have 3 apps using some shared library code, and it’s just magical when I save a file all 3 apps instantly have the code loaded.

Swing

Because the changes are so immediate, I just had to put together something I can use to give me a quicker feedback time on UI development:

(defn test-panel [panel & [title]]
 (-> (sw/frame :title (or title "Test Panel") :on-close :dispose :content panel) sw/pack! sw/show!))

(defonce scaffold-frame (atom nil))

(defn scaffold
 "Show or refresh a test panel using the given panel function to build the contents."
 [panel-fn]
 (if (nil? @scaffold-frame)
   (reset! scaffold-frame (test-panel (panel-fn)))
   (sw/config! @scaffold-frame :content (panel-fn))))

This is using seesaw to abstract swing out a bit, but I can include a call to scaffold somewhere at the root of a namespace and on reload, it updates my frame with the new panel, without moving or resizing it anywhere. Brilliant.

Summary

I’m really enjoying this new way of development – makes me feel way more in touch with what I’m building, gets my startup time quicker and the focus in the right place. This lightweight environment also encourages experimentation which gives you the freedom to try new ideas and different ways of doing things to see how they feel, instead of painstakingly trying to guess from a distance.

From → Coding

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: