Clojure provides two basic ways to interface with the host platform.
The more comprehending is gen-class which I touched in [a previous
post]gc. It's little brother is proxy. Although less powerful it
is more dynamic than gen-class. Let's see how it works…
So what is actually the difference between gen-class and proxy?
gen-class creates a named class while proxy does not
1. This has some serious
consequences. The most notable is that proxy cannot add methods to objects it
creates. So you can only implement interfaces and extend classes by
implementing their methods. On the other hand you don't need AOT compilation.
Otherwise proxy is similar to gen-class. The object instantiated by
proxy gets stubs for the methods which just look up usual clojure
functions in a map. This means proxy-methods are normal (though
anonymous) clojure functions and in particular they close over their
environment. If a method is not found in the proxy's map the stub throws
an UnsupportedOperationException or calls the super's method.
Let's compare proxy to our previous examples.
(defn make-some-example
[]
(proxy [Object] []
(toString [] "Hello, World!")))
As we don't get a named class from proxy but some anonymous object
there is obviously no constructor to call. So we just define a function
calling proxy.
user=> (.toString (make-some-example))
"Hello, World!"
In the above example we extended a pre-existing class – in this case Object.
But we can also implement interfaces without specifying a super class
2.
(defn make-some-example
[]
(proxy [clojure.lang.IDeref] []
(deref [] "Hello, World!")))
As you would expect, we can now use @ and deref on our object.
user=> @(make-some-example)
"Hello, World!"
The next step for gen-class examples was to add some state to
the object. While this was rather troublesome in the gen-class case –
we had to add a constructor and some state holding field – the situation in
the proxy case is much simpler. The methods are actually closures. So we can
simply close over any non-constant attributes!
(defn make-some-example
[msg]
(proxy [clojure.lang.IDeref] []
(deref [] msg)))
Now we simply pass the required parameters to the factory function.
user=> @(make-some-example "Hallo, Welt!")
"Hallo, Welt!"
And finally we can use the same trick to also allow modifications.
(defn make-some-example
[msg]
(let [state (atom msg)]
(proxy [clojure.lang.IDeref] []
(toString [] @state)
(deref [] state))))
As before we now simply close over the atom to allow access later on.
user=> (def o (make-some-example "Hallo, Welt!"))
#'user/o
user=> (.toString o)
"Hallo, Welt!"
user=> (reset! @o "Здравей, свят")
"Здравей, свят"
user=> (.toString o)
"Здравей, свят"
Please note how we had to hijack the deref method from clojure.lang.IDeref
since we cannot add a new method state as we did in the [gen-class
examples]gc. I don't recommend doing so in a real world program.
Since we can also override the methods of a super class with proxy we have
also the possibility of calling out to the super's method. One particular
application I had for this was to define some offset for a
LineNumberingPushbackReader.
(defn make-offset-reader
[reader offset]
(proxy [clojure.lang.LineNumberingPushbackReader] [reader]
(getLineNumber [] (+ offset (proxy-super getLineNumber)))))
So what happens? We create a proxy to a LineNumberingPushbackReader.
We use whatever reader we are given and pass it on to the super's
constructor. Since we proxy a class all methods which are not defined
for the proxy instance are forwarded to the corresponding super method.
We just intercept calls to .getLineNumber. Here we call the super method
but add the offset before returning the result.
There is two special cases concerning methods:
So consider a class like this one.
class Example {
void someMethod(String x) {
doSomethingWithString(x);
}
void someMethod(Integer x) {
doSomethingWithInteger(x);
}
}
Both methods are mapped to the same proxy method. So you have to test in your function to see, which version was the intended one.
(proxy [Example] []
(someMethod
[x]
(condp instance? x
String (.doSomethingWithString this x)
Integer (.doSomethingWithInteger this x))))
Another problem is connected to proxy-super. Again an example:
class Example {
void someMethod(String x) {
someMethod(x, null)
}
void someMethod(String x, String y) {
doSomethingWith(x, y);
}
}
You want to override the 2-ary version of the method. So you start out
with a pretty straight forward proxy call.
(proxy [Example] []
(someMethod [x y] (.doMoreStuff this x y)))
Arg. But this doesn't work, because the function is called for all
arities. So we have to add the 1-ary version also. But we don't want
to override the 1-ary version. No problem! We simply call proxy-super
as we saw above.
(proxy [Example] []
(someMethod
([x] (proxy-super someMethod x))
([x y] (.doMoreStuff this x y))))
Cool, eh? But not working as expected. Our overriden 2-ary version is
not called… To understand what's going wrong, we have to understand how
proxy-super works.
And that is quite easy. When calling a method on a proxy object, the
corresponding function is looked up in the proxy's method map. If there
is no entry, the super's method is invoked (or an
UnsupportedOperationException is thrown in case it was an interface
method). So what does proxy-super do? It simply remove the map entry,
calls the method in the proxy instance again and restores the map entry
afterwards.
But wait! What happens when inside our class the 1-ary version calls the 2-ary version? The map entry is missing and the super's method is called! Not our override as expected!
As the well-disposed reader might already have noticed there is another
difference between proxy and gen-class in terms of how methods are
defined. While the methods for gen-class take the object as first
argument (and can thus it can be named whatever you like) proxy
captures the symbol this in a similar way Java does. So in a proxy
method this will always refer to the instance at hand.
However there is a gotcha! Clojure does not stop you from creating a
local this. Together with proxy-super dangerous waters lie ahead.
(proxy [Object] []
(toString
[]
(let [this "huh?"]
(proxy-super toString))))
In this case we get an exception. But in general there might lurk subtle bugs. So watch out!
user=> (.toString (proxy [Object] []
(toString
[]
(let [this "huh?"]
(proxy-super toString)))))
#<CompilerException java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IProxy (REPL:2)>
So here a short summary.
proxy
gen-classthis and proxy-super detailsgen-class
In most cases a combination of proxy and gen-interface is to be preferred
over gen-class.
get-proxy-class)Object will be the super class.Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer