Clojure macros, reify and Java annotations. Oh my!
Using Java annotations with Clojure and deftype
, definterface
, etc. is a bit under-documented in my opinion.
There's an example from Rich Hickey, but even so, some more examples in
the official documentation would be quite nice. Especially so when using these functions combined with your own custom
macros, say if you want to generate classes/interfaces at runtime and pass in dynamic values for the annotation's
properties.
It just so happens I spent the bulk of the afternoon today when it was gorgeous outside sitting inside at my computer
fiddling about with reify and macros and applying Java annotations to the generated anonymous class methods. I spent a
fair bit of time on Google first but didn't find anyone who was doing quite the same thing ... there was some stuff on
gen-class
, but that wasn't really what I needed. Perhaps my Google-fu is weak.
Anyway, lets say that we are working with some random Java library and some method we want to use in it expects to be
passed an instance of some class with a calculate
method accepting two numbers. Yup, totally.
=> 3
Nothing special. And it works great too!
But what if said Java library would not run our calculate method unless it was decorated with some Java annotation? And that annotation has a property that needs to be set to different values based on the method of calculation! (Why, you ask? Because! Don't ask silly questions!)
The horror!
Now applying it to reify
is very similar to the example shown for deftype
in the Gist linked above:
We can verify that the annotation exists on the method:
=> #<$Proxy6 @testapp.AmazingAnnotation >
Great! But we don't want to hard-code the :foobar
value, we need that to be specified at runtime from some other
source.
=> CompilerException java.lang.UnsupportedOperationException: Can't eval locals ...
Hrm, well... I didn't know that it wasn't possible to do that, but now I do. So I guess we need a macro! And this for
me was the fun part, because you have to use something like with-meta
, vary-meta
, etc. because you want to apply the
annotation metadata to the form emitted by the macro, not the forms in the macro itself!
And to verify that it does work:
=> #<$Proxy6 @testapp.AmazingAnnotation >
In the end, I think what messed me up for the longest time was getting the right combination of quoted/unquoted forms
in the macro and wrapping the exact right form in the call to with-meta
. Hope this helps someone else out there.