Chris Umbel

Clojure, A Lisp for the JVM and CLR

I've been becoming increasingly interested in functional languages in the last few years and I'm apparently not the only one. It's pretty hard to listen to any general purpose software development podcast without hearing about Erlang, Haskell or F#. Another one came up recently that I just had to play with. It's a Lisp variant named Clojure.

Clojure Logo The reason I find Clojure particularly interesting is that it's designed to be hosted in the Java Virtual Machine and the .Net Common Language Runtime (via the DLR). From a practical perspective that's wonderful considering integration with other commonly used libraries in the business world is a snap. I'm sure it annoys Lisp purists, but it makes Clojure much more adoptable between 9 and 5.

As is frequently the case this is not something I'm an expert in and am by no means qualified to write a proper tutorial. I would, however, like to share some of the code I whipped up while aquainting myself with Clojure in the hopes it can help other Clojure neophytes. I'll cover the basic language, JVM/CLR integration and Software Transactional Memory (STM).

Factorials, the Classic Example

To demonstrate the basics for someone unfamilier with Lisp I'm going to take an approach that I haven't taken in my previous demonstrations. Here I'm going to solve the same problem a few different ways. It's a common, simple and functional problem: factorials. Consider all the following functionally equivalent function declarations:

Factorial Style 1: Recursion

(defn fact[n]
    (if (= n 1) 1
    (* n (fact (
    dec n)))))

There's a simple, more imperitive looking recursive implementation. By no means idiomatic in Lisp terms, but mathematically correct.

Factorial Style 2: Tail Recursion

(defn fact [n]
    ;; this is where tail recursion enters
    (loop [cnt n acc 1]
	;; we're done
        (if (= 1 cnt) acc
            ;; otherwise recurse
           (recur (dec cnt) (* acc cnt)))))

Example 2 uses the recur-loop form which, while recursive, won't consume additional stack space as style 1 would. Hence the benefit of tail recursion optimization.

Factorial Style 3: Reduction

(defn fact [n]
    (reduce * (range 1 (inc n))))

The approach in example 3, while iterative, is perhaps the most interesting. It uses the reduce and range functions that would be familiar to python programmers. reduce essentially starts out by applying a specified function, multiplication in this case, to two members in a set. The result of the function and the next member in the set are then fed back into the reduction function until the entire set has been processed. The range function is used here to produce a sequence upon which reduce will iterate.

Java Runtime

Now that we've covered some basics let's look at some interaction with the Java runtime. The following is my classic example of twitter status retrivial via the REST API:

(import '(javax.xml.parsers DocumentBuilderFactory DocumentBuilder)
        '(javax.xml.xpath XPathFactory XPath XPathExpression XPathConstants))

;; function to retrieve a twitter user's status
(defn getStatus[userName]
	(let [domFactory (. DocumentBuilderFactory newInstance)
		builder (. domFactory newDocumentBuilder)
		;; build the url 
		url (str "http://twitter.com/users/" userName ".xml")
		;; load contents located at the url into the builder
		doc (. builder parse url)
		;; create xpath plumbing
		factory (. XPathFactory newInstance)
		xpath (. factory newXPath)
		expr (. xpath compile "/user/status/text/text()")]
		;; pull the user's status out of the document
		(. expr evaluate doc (. XPathConstants STRING))))

(println (getStatus "chrisumbel"))

CLR

Here we go again with a twitter status example, this time in the .Net runtime.

(System.Reflection.Assembly/Load "System.Xml,
	Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
(import '(System.Xml XmlDocument))

;; function to retrieve a twitter user's status
(defn getStatus[userName] 
	(let [
		;; build the url 
		url (str "http://twitter.com/users/" userName ".xml")
		;; instanciate an XML document and load the contents located
		;; at the url into it
		doc (doto (XmlDocument.)
		(. Load url))]
		;; pull the user's status out of the document
		(. (. doc SelectSingleNode "/user/status/text") InnerText)))

(println (getStatus "chrisumbel"))

Speaking of XML

The above examples do a fine job of illustrating how to dig into the class libraries of the respective runtimes but they're anything but idiomatic because Clojure has it's own functional XML parsing system. Check out this post about xml-seq for details.

Software Transactional Memory

It's hard to discuss a language these days without evaluating how it handles concurrency. Clojure does not disappoint in this regard as it brings Software Transactional Memory (STM) to the table. Note the dosync call on line 11. Anything that happens within that scope is effectively wrapped in a transaction and is atomic. If a conflict occurs the transaction will be retried.

(import '(java.util.concurrent Executors))

(let [ val (ref 0)
    ;; thread pool will contain 4 threads
    pool (Executors/newFixedThreadPool 4)
    ;;  create a list of 4 tasks that count to 1000
    tasks (map (fn [t]
        (fn []
          (dotimes [n 1000]
            ;; start a transaction
            (dosync
              (doseq []
                ;;  add one to the val
                (alter val + 1)
                (println (str "OP1: " t " : " n " : " (deref val)))
                ;;  add another one to the val
                (alter val + 1)
                (println (str "OP2: " t " : " n " : " (deref val))))))))
        (range 4))]
    ;; spawn the threads
    (doseq [future (.invokeAll pool tasks)]
        (.get future))
        (.shutdown pool)))

Conclusion

Clojure is an interesting combination of old and new. Lisp was designed in 1958 and is the second oldest high level language. The virtual machines that Clojure runs on are far newer and concepts like STM are only now making their way into the mainstream. Somehow all of these technologies still appear to fit together at least well enough to the job done. I look forward to attempting a serious project in Clojure and definately recommend giving it a look.

Relevant Links

Sun Dec 13 2009 21:12:00 GMT+0000 (UTC)

Follow Chris
RSS Feed
Twitter
Facebook
CodePlex
github
LinkedIn
Google