in which the lesser-known are brought to the forefront

«

»

In my time hacking in Clojure I've found a bunch of under-appreciated Clojure libraries of which I'm rather fond. I thought it'd be helpful to share, so here they are:

lein-multi

As support for Clojure 1.3 becomes widespread, it becomes more important for projects to think about backwards compatibility. You can specify a range of versions for Clojure in project.clj with [org.clojure/clojure "[1.2.0,1.3.0]"], but when you're developing, it will just find the latest and pull that in, so it's easy for dependence on 1.3-specific features to sneak in.

Enter lein-multi, a plugin for running arbitrary tasks against multiple dependency sets. It's most commonly used with the test task, but it's a general higher-order task that can be applied to any other just as well. Seeing lein-multi in someone's project.clj is a good indicator that they take backwards-compatibility seriously.

Update: Leiningen 2 supports this out of the box using profiles, so the lein-multi plugin is deprecated.

[lein-multi "1.0.0"]

clj-stacktrace

stack trace

The most common complaint I hear about developing in Clojure is the fact that its error messages often obscure the true cause of the problem. While practice helps here, it's still true that there's a lot of irrelevant detail here that can overwhelm the trained eye. Mark McGranaghan's clj-stacktrace library does a great job at summarizing stack traces by aligning each frame, making the namespace distinct from the function name, and even coloring frames differently based on whether they come from Clojure, Java, or user code. The next version should let you filter out frames that are deemed irrelevant on a per-project basis.

To use it in your project's repl as well as in clojure.test error reporting, place this code in ~/.lein/init.clj after running $ lein plugin install clj-stacktrace 0.2.4:

(require 'leiningen.hooks.clj-stacktrace-test)

(def settings {:repl-options [:init (require 'clj-stacktrace.repl)
                              :caught 'clj-stacktrace.repl/pst+]})

slingshot

Another common question you hear is "How do I generate custom exception classes?". It's awkward and somewhat un-idiomatic to do this, so people generally try to get by with the Exception classes that ship with the JDK. But why shouldn't exceptions get the same level of dynamicity and flexibility that Clojure affords other data types?

This is basically the question Slingshot addresses. It provides enhanced try+ and throw+ counterparts to the built-in error mechanisms that let you throw arbitrary data types like maps. Then rather than dispatching in your catch blocks based on class, you can use arbitrary predicates. You can even perform destructuring on maps that are thrown. It's a big step up in expressiveness:

(defn asplode! []
  (throw+ {:bad? true :tachyon-level 21}))

(defn ignorable? [e]
  (and (:silent e) (not (:fatal? e))))

(try+ (asplode!)
  (catch ignorable? _)
  (catch :bad? e
    (log/warn "Bummer dude!" e))
  (catch :fatal? {:keys [exit-code]}
    (System/exit exit-code)))

Update: The equivalent of the exception-throwing side of Slingshot has been included in Clojure 1.4 using the ex-info and ex-data functions, so unless you are targeting old versions of Clojure you should use those instead. Nothing has been added on the exception handling side though, so Slingshot's try+ macro is still very useful.

[slingshot "0.8.0"]

lein-difftest

If you've ever written a test where you expect two lengthy data structures to be equal, you'll remember how annoying it is to try to compare the failure message where "expected" and "actual" are each spat out on a single line and you're supposed to try to hunt down the difference. Using lein difftest makes it easy:

difftest example

It also uses clj-stacktrace to report errors. This one is better off installed as a user-level plugin since you're likely to want to be able to use the difftest task across all your projects:

$ lein plugin install lein-difftest 1.3.7

Update: Use version 2.0.0 of lein-difftest for compatibility with Leiningen 2.x.

robert-bruce

From the do-one-thing-and-do-it-well department, we have Robert Bruce, which concerns itself only with determinedly retrying a given function. It provides all the options you could imagine for how to perform the retries, including pausing between them (with exponential decay on backoffs), retry limits, callbacks on failure, and so on.

[robert/bruce "0.7.1"]

Happy hacking!

Do you have a favourite that I've missed here? Leave a comment about it. Remember that the version numbers provided here are current as of the time of this writing but may be outdated when you read this.

« older | 2011-10-28T17:21:54Z | newer »