More Clojure

26 February 2010 17:59

I've spent a few more days playing with Clojure. I've found it to be fun and challenging, but I can't really argue useful in any way yet.

I guess this isn't surprising. There are a few times when you would expect advantage from using Clojure: when you need a lot of heavy concurrency (so you can't just do things asynchronously), when you want to integrate with stuff on the JVM, and when you understand LISP enough for macros to make the language genuinely more powerful than other languages.

On the negative side, I've been confused by a few things.

Boxing

One of the expected ones was the boxing of low-level types. If you want to play with Java libraries, you might need to pass unboxed values around. But as soon as you touch anything in Clojure, it's automatically boxed. Calling libraries with values is fine: Clojure does some magic, but if you need to pass, say, an array of floats, you need to convert your Clojure vector explicitly. I made the mistake of expecting this to work:

(into-array [(double 1) 2.0 3.0])

Since into-array is a function which creates an array in which everything is the same type as the first item. However, what actually happens is that the double has to be boxed to be put into the Clojure vector, so you get an array of boxed doubles. The correct solution is:

(double-array [1 2 3])

Java libraries

In order to use ChartDirector with Clojure I had to download a couple of web libraries (servlet-api and jsp-api) and add them to my classpath. This wasn't too painful when I worked out what to get, but there are multiple jars out there containing the classes I needed, and I don't know what might be an official source of them.

Functional programming

I'm still to be convinced that the purists from either the functional or the OO world have a point. Generally I've liked the functional side of Clojure, but today I wanted to create an exponentially weighted moving average function. It took me a long while! Here's what I came up with:

(defn ema
([f values]
  (ema f (rest values) (first values)))
([f values running]
  (if (empty? values)
    (list running)
    (conj (ema f (rest values) (+ (* f (first values))
                                  (* (- 1 f) running))) running))))

Although this seems to work, it won't work with very large lists: I need to thread an accumulator through so that tail-call optimisation is possible, and then use Clojure's recur since the JVM doesn't support tail-call optimisation. I also haven't thought very carefully about whether I should be using lists or vectors. Probably the performance is good enough with both, but it's another thing to think about.

After a while writing this, I wonder how much effort it would take in Python. Answer, about two minutes:

def ema(f, values):
   running = values[0]
   for v in values:
       running = f*v + (1-f)*running
       yield running

A lot simpler. (I'm not arguing that Python is always this much clearer; I'm just making the point that some things don't seem to be suited to the functional approach).

Maybe the Clojure code could be more like the Python by using the lazy-seq macro. I couldn't see how immediately, though.

Brain size

I'm not sure whether Clojure could ever fit in my brain. There are sooo many functions and macros. And often they are synonyms which do different things. For example vector returns a vector containing its arguments, whilst vec takes a collection as an argument and returns a vector containing its contents. I haven't found the equivalent of vec for lists yet!

Inlining

I spent ages early on trying to figure out why some code (which wasn't correct) behaved in inconsistent ways. It turned out to be related to function inlining. I haven't looked into this in depth, but I'm not the only person who's been confused by it. Looking at the source for + you can see the inline option:

(defn +
 "Returns the sum of nums. (+) returns 0."
 {:inline (fn [x y] `(. clojure.lang.Numbers (add ~x ~y)))
  :inline-arities #{2}}
 ([] 0)
 ([x] (cast Number x))
 ([x y] (. clojure.lang.Numbers (add x y)))
 ([x y & more]
  (reduce + (+ x y) more)))

Comments

Josh Tilles wrote on 29 March 2013:

After a bit of playing around, here's my implementation of your exponentially-moving-average function: https://gist.github.com/MerelyAPseudonym/5273905.

It was a fun little challenge! Knowing to look for a function like clojure.core/reductions was key. I suppose that relates to your sense of Clojure having an overwhelming number of functions & macros =) In my experience, familiarity does arrive eventually. Sites like [Clojure Atlas](http://www.clojureatlas.com) and [4Clojure](http://www.4clojure.com) help a lot, especially for beginners.

Anyway, your post was a nice read. I don't know if you're still doing much with Clojure, but I think it's valuable for people to share their experiences when exploring something new.

Graham wrote on 29 March 2013:

Thanks for your response, Josh. I wasn't aware of reductions - as you say, that's exactly what's needed here. I am still playing with Clojure sometimes and considering using ClojureScript for a web project, but Python is still my preferred tool for problems with deadlines. Right now I'm reading Learn You a Haskell, though.

Leave a comment