Dynamic proxies

81 views
Skip to first unread message

Rich Hickey

unread,
Mar 1, 2008, 9:31:14 AM3/1/08
to Clojure
Clojure has offered the ability to create instances of interfaces
dynamically through its implement macro. This feature was built upon
the java.lang.reflect.Proxy system, and, like it, was limited to
deriving from interfaces only.

I've added to Clojure (in SVN as of rev. 708, not yet in release)
direct dynamic proxy generation through the new proxy macro. It works
just like implement with these differences:

The first item in the vector of supers can be a non-interface
class. (If no class is provided it defaults to Object)

The vector of interfaces is followed by a (possibly empty)
vector of arguments to the superclass constructor.

Each method fn is now implicitly passed an additional first arg
called this, whose value is the proxy obect itself.

A method fn can have multiple arities.

A method fn can override a public or protected method of the
superclass.

It is possible (using update-proxy) to alter the method/fn map
of an existing proxy, and thus change its behavior, without altering
its identity.

Proxies supercede implement, and all usage of implement should be
changed to proxy, which is likely to offer better performance.

The use of proxy to implement interfaces is still primary. Deriving
from concrete classes is not something I encourage generally. But
we've all been faced with libraries that unfortunately require
concrete derivation in order to interoperate - now with proxy you can.

There are some limitations of proxying vs direct derivation. While
method fns can override protected methods, they have no other access
to protected members, nor to super, as these capabilities cannot be
proxied.

Please switch over your code to use proxy instead of implement, and
try it out in situations where you need to derive from non-interface
classes, and let me know how it works for you.

Rich

p.s. For those interested in bytecode generation and compiling, the
implementation of proxy is written in Clojure (src/proxy.clj), and
provides and example of using the embedded ASM bytecode library from
Clojure itself.

smith...@googlemail.com

unread,
Mar 1, 2008, 3:03:33 PM3/1/08
to Clojure
I was able to successfully derive from java.io.InputStream and
override methods. But it seems to be impossible to add any new
methods, i.e. methods that don't override parent methods. I reckon
this is a limitation of dynamic proxies in general (right?) but it
would have been nice to have that possibility e.g. to provide accessor
methods to vars captured in a closure:

(defn make-instance [arg]
(proxy [my.parent.Class] []
(get-arg [] arg)
...

Nevertheless a nice new feature that helps a lot. Thanks!

- John

Rich Hickey

unread,
Mar 1, 2008, 4:51:59 PM3/1/08
to Clojure


On Mar 1, 3:03 pm, "smith49...@googlemail.com"
<smith49...@googlemail.com> wrote:
> I was able to successfully derive from java.io.InputStream and
> override methods. But it seems to be impossible to add any new
> methods, i.e. methods that don't override parent methods. I reckon
> this is a limitation of dynamic proxies in general (right?) but it
> would have been nice to have that possibility e.g. to provide accessor
> methods to vars captured in a closure:
>
> (defn make-instance [arg]
> (proxy [my.parent.Class] []
> (get-arg [] arg)
> ...
>

It's not an inherent limitation of proxies, but I've left it out for
now for a number of reasons:

I want to cache the proxy classes rather than generate a new class
for each proxy call. If different proxy calls with the same supers
could have different method sets (they can't now, the method sets are
the union of the methods of the supers, regardless of which ones you
overide/implement), caching would be of limited use.

The dynamic update facility couldn't be extended to adding new
methods.

The generated proxy types are anonymous, so no one could know, in
a typed manner, about the additional methods - they would always have
to be called via reflection. Even though it supports it, Clojure is
not really a duck-typing oriented language.

Because of the previous consideration, it seemed to me the best thing
for someone to do would be to have an interface that described the
additional methods, and then just use that interface in the proxy. An
extremely useful general interface one might want to support in order
to extend access to possibly dynamic 'members' is IPersistentMap. Your
resulting proxy would work with all the map APIs:

(:arg instance-of-your-proxy)

See bean for an example.

> Nevertheless a nice new feature that helps a lot. Thanks!
>

You're welcome!

Rich

John Cowan

unread,
Mar 1, 2008, 4:55:52 PM3/1/08
to clo...@googlegroups.com
On Sat, Mar 1, 2008 at 4:51 PM, Rich Hickey <richh...@gmail.com> wrote:

> Because of the previous consideration, it seemed to me the best thing
> for someone to do would be to have an interface that described the
> additional methods, and then just use that interface in the proxy.

Sounds right. To make that straightforward it would be useful to be
able to create an interface from Clojure, something like this:

(interface classname (classname methodname classname ...) ...)

--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

Rich Hickey

unread,
Mar 1, 2008, 5:10:55 PM3/1/08
to Clojure


On Mar 1, 4:55 pm, "John Cowan" <johnwco...@gmail.com> wrote:
> On Sat, Mar 1, 2008 at 4:51 PM, Rich Hickey <richhic...@gmail.com> wrote:
> > Because of the previous consideration, it seemed to me the best thing
> > for someone to do would be to have an interface that described the
> > additional methods, and then just use that interface in the proxy.
>
> Sounds right. To make that straightforward it would be useful to be
> able to create an interface from Clojure, something like this:
>
> (interface classname (classname methodname classname ...) ...)
>

As I've said before, class names, and thus interface names, and their
definitions are static things, and there is an impedance mismatch
between them and the dynamic nature of Clojure. Were I to start
supporting things like the above, it would be the one part of Clojure
you couldn't re-execute after changing, or fix a problem in without
restarting.

I don't even think there are tremendous advantages to the s-expression
rendition vs, the Java equivalent (Java being a static typed language
is good at defining static typed things), but if one did, you could
always define:

(gen-interface classname (classname methodname classname ...) ...)

which spit out Java source.

Rich

John Cowan

unread,
Mar 1, 2008, 6:10:14 PM3/1/08
to clo...@googlegroups.com
On Sat, Mar 1, 2008 at 5:10 PM, Rich Hickey <richh...@gmail.com> wrote:

> Were I to start
> supporting things like the above, it would be the one part of Clojure
> you couldn't re-execute after changing, or fix a problem in without
> restarting.

Fair enough.

> (gen-interface classname (classname methodname classname ...) ...)
>
> which spit out Java source.

Okay, cool. Now Clojure needs an interface to Janino <http://www.janino.net>.

Reply all
Reply to author
Forward
0 new messages