lice! : On clojure libs and versions.

On clojure libs and versions.

Between clojure 1.1.0 and 1.2.0 a lot will change and every library maintainer has to make the decision how or what to support. Basically there are three general options.

  • Make it 1.1.0 compatible
  • Make it 1.2.0 compatible
  • Make the effort of making the library compatible for both versions.

Option 1 and two are trivial and will not change much in the development process so for the third, and in my eyes best option some work is needed. I’ve went through this for one of my own libraries and with the great help of the mailing list and the #clojure channel succeeded. Now I figure I share my experience to help others make their libraries more compatible.

Namespaces

The first issue is changing namespaces, as an example in clojure 1.1 the namespace clojure.contrib.duck-streams holds io utility functions, in clojure 1.2.0 it is renamed to clojure.contrib.io. So if you have code like (I take the worst case here):

(:ns my.cool.app
  (:use  clojure.contrib.duck-streams))

it will stop to work with 1.2.0, the trick are require and aliases:

(:ns my.cool.app)

(try
  (require '[clojure.contrib.duck-streams :as io])
  (catch Exception e (require '[clojure.contrib.io :as io])))

So what we basically do is trying to require the clojure.contrib.duck-streams namespace and try to alias it to io, if it does not exist (since we are in clojure 1.2) a exception is thrown, the catch clausal now catches this exception and requires clojure.contrib.io instead aliasing this to io. In the end either one or the other will be aliased to io to the remaining program it is transparent where io comes from when calling (io/reader …) for example.

Classes

Another topic are classes, this is very unlikely to hit you but since it might happen I’ll talk about it. Clojure 1.2.0 deprecates a few classes, removing them entirely. So say you, for some reason need clojure.lang.Stream for some reason, this does not exist in 1.2.0 and will throw ugly exceptions when someone tries to use your library with 1.2.0. Ugh!
What to do? Well I can’t tell you how to replace clojure.lang.Stream but I can tell you how not to get an exception and gracefully handle the situation.

The first problem is that, the second the reader sees clojure.lang.Stream it will scream explode and leave ugly smudges on the floor. And that will happen at compile time nothing you can do about that, no try catch no if nothing. So what to do? Simple don’t write it!

(try 
  (let [StreamClass (resolve 'clojure.lang.Stream)]
    (do stuff with StreamClass))
  (catch Exception e (do whatever you do when there is no 'clojure.lang.Stream)))

So why does this work? The trick is resolve, notice the ’ in front of clojure.lang.Stream makes it a symbol instead of a class the reader and compiler don’t care of a class with this name exists then, you could write ‘i.want.cookies and probably you won’t get any from the compiler, neither cookies nor exceptions.
Resolve will – at runtime – try to make ’clojure.lang.Stream the class clojure.lang.Stream, if it fails it throws an exception but one you can catch and handle! How you handle it is your problem so, sorry ;).

Being nice to the users (making the right dependencies!)

So now you have a library that runs in clojure 1.1.0 and 1.2.0, great! But I’ll bet you a cookie that your leiningen project.clj (or maven pom) says [clojure “1.1.0”] or [clojure “1.2.0”] that is bad bad bad!
Why is this bad? It in the end binds your library to exactly this version, so if you have [clojure “1.1.0”] and someone wants to use it with 1.2.0 leiningen (or maven) will whine and do odd and scary things! Same if you have 1.2.0 and someone wants to use it with 1.1.0.
How to handle this? Again it is pretty simple, maven (and leiningen uses maven for dependencies) has version ranges so if you want to tell maven ‘I want clojure 1.2.0 or 1.1.0 or something in between’ you use [clojure “[1.1,1.2]”]. And there you go, now your library won’t cause a fuss!

As a note: don’t use [clojure “[1.1.0,1.2.0]”] this will break horribly!

Well this is all, I hope it helped.

Posted by Heinz N. 'Licenser' Gies Thu, 22 Apr 2010 21:35:00 GMT


Trackbacks

Use the following link to trackback from your own site:
http://blog.licenser.net/trackbacks?article_id=61

Comments

  1. Avatar
    Phil about 19 hours later:
    Good stuff! I used to suggest that libraries declare Clojure as a dev-dependency, since it's assumed that all users of the library would already be using Clojure anyway. But using a version range is even better, and it works with Contrib too.