Apparently if a ctor of a base class calls a method that's
overridden by a derived class, the *derived* class's
implementation is called (unlike in C++ where the base class's
own impl would be called). This is apparently not recommended
but it happens, and as Lau Jenson discovered some libraries
depend on this behavior.
Here's an example of just such an evil base class:
(ns joy.proxytest)
(gen-class
:name joy.proxytest.evilbase
:prefix "evilbase-"
:post-init ctor
:methods [[tragedy [] void]]
:impl-ns joy.proxytest)
(defn evilbase-ctor [this]
(println "evilctor before tragedy")
(.tragedy this)
(println "evilctor after tragedy"))
(defn evilbase-tragedy [this]
(println "this is meant to be like pure virtual"))
The idea here is this class is meant to be extended by a derived
class that implements it's own .tragedy method, which will be
called during object construction by evilbase.
If we try doing exactly that with 'proxy', we run into a problem:
(proxy [joy.proxytest.evilbase] []
(tragedy [] (println "my own tragedy")))
; evilctor before tragedy
; this is meant to be like pure virtual
; evilctor after tragedy
What's happening here is that proxy doesn't hook up our .tragedy
implementation until after it has finished creating the object.
So when evilbase calls .tragedy, the proxy falls back on the
.tragedy impl given by the base class -- evilbase itself in this
example.
I see no way to work around this using proxy as it is. For what
it's worth, implementing the derived class using gen-class does
work:
(gen-class
:name joy.proxytest.hope
:prefix "hope-"
:extends joy.proxytest.evilbase
:impl-ns joy.proxytest)
(defn hope-tragedy [this]
(println "gen-class gives us the power"))
(joy.proxytest.hope.)
; evilctor before tragedy
; gen-class gives us the power
; evilctor after tragedy
Anyway, I was curious if there was any appetite to fix this.
I do so dislike using gen-class compared to proxy and reify that
I thought it might be worth some effort.
The only fix for proxy that I've thought of would be have
a thread-local object that would point to the proxy function-map
from just before the proxy obj was created until just after it
was done being created, or until one of the proxy methods is
called, whichever comes first. Each proxy stub would check for
the instance's own function-map as it does now, but if the map
itself was nil it would fall back to checking the thread-local
map which if found would be promptly installed and used (and the
thread-local value cleared in case this proxy method went on to
create some other proxy object).
It's a bit messy, but I think it would work. The ASM required
would be a stretch for me, but it might be worth figuring out how
to do it, compared to trying to document the behavior in some
book or other...
--Chouser
http://joyofclojure.com/
I would say though, that it's definitely worth solving. This is a
particularly esoteric corner of Java's object model, and regardless of
the merits or failings of the model, if proxy is going to provide for
extension of concrete classes, should meet the contract of the host
platform w.r.t. the interop features it supports. 98% of programmers,
regardless of their history with Java, would not think about this
issue when using proxy; given the subtle bugs that it can lead to, if
there's any way to solve the problem, it's worth it.
2¢ from someone who wouldn't have a clue how to solve it himself. ;-)
- Chas
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure Dev" group.
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to clojure-dev...@googlegroups.com
> .
> For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en
> .
>
For the record, Rich addressed this in IRC:
"I'm ok with saying - sorry, the evil class made you use gen-class"
http://clojure-log.n01se.net/date/2010-04-12.html#08:58-09:08
--Chouser
http://joyofclojure.com/