Saturday, December 13, 2008

Using Clojure to Invoke Groovy Methods

Clojure is a LISP dialect that runs on the JVM. It allows you to seamlessly use Java APIs such as JDBC, Swing, Hibernate, XML parsers, etc. When I do simple scripting jobs such as ripping apart an XML file or querying a database to do some complicated calculation on the result set, I like to use Groovy. It's easy-to-use APIs (e.g., groovy.sql.Sql and groovy.util.XmlParser) make such jobs relatively painless. I hesitate to give these up when using Clojure, which brings me to the point of this article.

Clojure can use parts of the Groovy APIs. The following example use groovy.util.XmlParser. First, put groovy-all-1.5.4.jar on the Clojure classpath, then run the following Clojure code.

(import '(groovy.util XmlParser)
'(javax.xml.parsers SAXParser SAXParserFactory))

(def xml
"<a>a node
<b>b node
<c>c node</c>
<d>d node</d>
</b>
<e>e node</e>
</a>")

(defn parse-xml-string [xml f]
"parse xml string and apply function f to a groovy.util.Node instance"
(let [parser (XmlParser. (.. SAXParserFactory newInstance newSAXParser))
node (. parser (parseText xml))]

(f node)))

(defn print-node-list [nodes]
"recursively print flattened node list"
(when (first nodes)
(println (first nodes))
(print-node-list (rest nodes))))

(defn main []
"breadthFirst, depthFirst, children are methods on the groovy.util.Node class"

(println "\nBreadth First\n")
(print-node-list (parse-xml-string xml #(. %1 breadthFirst)))

(println "\nDepth First\n")
(print-node-list (parse-xml-string xml #(. %1 depthFirst)))

(println "\nCount First Level Children of Root Node\n")
(println (count (parse-xml-string xml #(. %1 children)))))

(main)


The parse-xml-string function invokes the parseText method on the XmlParser, returning an instance of groovy.util.Node. The second argument to parse-xml-string is the function to apply against the node. I define three different anonymous functions inline in the main function.

At this point, I haven't done anything that you could not accomplish with Groovy by defining three closures. The second example shows an area where LISP shines: the macro capability. Rather than define three anonymous functions, I would like to pass in the name of the Node method (e.g., breadthFirst, depthFirst, children) as the second argument to parse-xml-string. Unfortunately, the compiler balks because it doesn't recognize these three terms. The solution, in lambda calculus terms, is to define an abstraction rather than an application. Do this by defining a macro, which gets expanded prior to compilation.

The following example uses defmacro to make new versions of parse-xml-string and main:

(defmacro parse-xml-string [xml method]
"parse xml string and call method on groovy.util.Node instance"
`(let [parser# (XmlParser. (.. SAXParserFactory newInstance newSAXParser))
node# (. parser# (parseText xml))]

(. node# ~method)))

(defn main []
"breadthFirst, depthFirst, children are methods on the groovy.util.Node class"

(println "\nBreadth First\n")
(print-node-list (parse-xml-string xml breadthFirst))

(println "\nDepth First\n")
(print-node-list (parse-xml-string xml depthFirst))

(println "\nCount First Level Children of Root Node\n")
(println (count (parse-xml-string xml children))))

(main)

The main function is simpler now. It is DRYer (i.e., don't repeat yourself) because it eliminates repetitive anonymous function code. Note the backquote and tilde in parse-xml-string macro. The backquote is a templating technique; the tilde is a substitution marker. I have appended a # mark to the local variables to generate on-the-fly variable names, avoiding namespace issues when using defmacro.

There are probably areas in Groovy beyond Clojure's reach. For example, I have not tried Groovy builders or other tools that rely on the methodMissing and propertyMissing functionality. I also have not tried to use any Groovy methods that require closures. Having said that, there is still rich functionality in Groovy that Clojure programmers can successfully utilize.

No comments: