Monday, December 12, 2011

Implementing Java Interfaces in Clojure

I recently ran into some hiccups while trying to implement a Java interface in Clojure. While it the documentation my make it appear seamless, there are a few quirks. I'll explain the different ways I've found of implementing an interface and the hangups/quirks of each.

(Note: I'll be implementing an Interface called "RequestHandler", which is part of a namespace called "HttpServer")

:gen-class


The unfortunate truth about this method is that it requires AOT compilation. So, in order to get this example to compile, you must add :aot [implement-java-interface.first-request-handler] to your project.clj file. Here's what it looks like.

(ns implement-java-interface.first-request-handler (:gen-class :implements [HttpServer.RequestHandler])) (defn -canRespond [this request] true) (defn -getResponse [this request] nil) implement-java-interface.main=> (.canRespond (implement_java_interface.first_request_handler.) nil) true
Looks pretty simple right? The first thing to notice is that to instantiate the class I had to call implement_java_interface.first_request_handler instead of implement-java-interface.first-request-handler. Because of the way that Clojure resolves namespaces, any dashes in namespaces are converted to underscores for file names. Therefore, we generated a class with underscores. There are a few ways to fix this. First, we can use the :name parameter of :gen-class

(ns implement-java-interface.first-request-handler (:gen-class :name implement-java-interface.first-request-handler :implements [HttpServer.RequestHandler])) (defn -canRespond [this request] true) (defn -getResponse [this request] nil) implement-java-interface.main=> (.canRespond (implement-java-interface.first-request-handler.) nil) true
Second, we can write a constructor method in the namespace,

(ns implement-java-interface.first-request-handler (:gen-class :implements [HttpServer.RequestHandler])) (defn -canRespond [this request] true) (defn -getResponse [this request] nil) (defn new-first-request-handler [] (implement_java_interface.first_request_handler.)) implement-java-interface.main=> (use 'implement-java-interface.first-request-handler) nil implement-java-interface.main=> (.canRespond (new-first-request-handler) nil) true
This is the preferred method. It allows us to use more Clojure-like constructs to create an object, i.e. calling a method within a namespace as opposed to calling a constructor on a class.

deftype/defrecord


Using deftype or defrecord is certainly a little cleaner than :gen-class and it doesn't require AOT compilation. Here's what it looks like.

(ns implement-java-interface) (deftype second-request-handler [] HttpServer.RequestHandler (canRespond [this request] true) (getResponse [this request] nil)) implement-java-interface.main=> (require 'implement-java-interface.second-request-handler) nil implement-java-interface.main=> (.canRespond (implement_java_interface.second-request-handler.) nil) true
Once again, our clean code is befuddled by the way Clojure resolves namespaces. Let's fix this by putting a method on the namespace.

(ns implement-java-interface.second-request-handler) (deftype second-request-handler [] HttpServer.RequestHandler (canRespond [this request] true) (getResponse [this request] nil)) (defn new-second-request-handler [] (second-request-handler.)) implement-java-interface.main=> (use 'implement-java-interface.second-request-handler) nil implement-java-interface.main=> (.canRespond (new-second-request-handler) nil) true
Excellent! We were able to use a method on the namespace to construct the class for us, just like the first example. Plus, this method doesn't require AOT!

reify/proxy


Last, but not least, let's use the reify feature of Clojure to implement an interface.

(ns implement-java-interface.third-request-handler) (defn new-third-request-handler [] (reify HttpServer.RequestHandler (canRespond [this request] true) (getResponse [this request] nil))) implement-java-interface.main=> (use 'implement-java-interface.third-request-handler) nil implement-java-interface.main=> (.canRespond (new-third-request-handler) nil) true
As you can see, reify is the cleanest of all the examples and works like we want it too right away.

Conclusion


Of the three, I would have to say that it's a close call between deftype and reify, but reify is my favorite. The two methods are very similar, however deftype is a little confusing with the namespace issues and constructing, but reify has the same benefit of not using AOT and is very succinct and clean as well. So, in the end, I will be using reify.

2 comments: