On Sun, Jan 15, 2012 at 5:26 PM, Norman Gray <norman.x.g...@gmail.com> wrote:
> Section 6.6.2 of the JLS mentions that "A protected member or constructor of 
> an object may be accessed from outside the package in which it is declared 
> only by code that is responsible for the implementation of that object"  
> (this is echoed in Sect 2.7.4 of the JVM spec). Now, the text which follows 
> that seems to boil down to saying that subclass implementations can see 
> protected instance fields or methods, which we all know, of course.
>
> Also, you established that methods overridden in a proxy are public.

Also, that it overrides all of them whether you provide an
implementation or not. A few tests seem to confirm that the default
implementations are just call-super implementations (when the
overridden method is concrete, anyway). More on that anon.

> There seem to be a couple of possibilities.
>
>  * There's an edge-case of the legalese in JLS 6.6.2 or JVMS 2.7.4 or 5.4.4 
> that is triggered by Clojure's implementation of the proxy class, and which 
> is beyond my current forensic powers to spot.

Further down in the weeds than I've yet gotten while investigating this.

>  * Or there's a special case.  The 'generate-proxy' function in 
> <https://github.com/clojure/clojure/blob/master/src/clj/clojure/core_proxy.clj>
>  does mention "finalize" in a test which apparently excludes specifically 
> that method from a "set of supers' non-private instance methods", but I can't 
> work out quite what it's doing with those.  It _may_ be that the goal there 
> is to automatically call super.methods for each method in the proxy, and I 
> can see why one would want finalize() to be exempt from that, but if so, it 
> may be this code which is, inadvertantly or not, preventing extending-classes 
> adding finalizers.

Seems likely then that it's either an intentional limitation, or a bug
resulting from preventing the finalizer from automatically starting
with super.finalize(). A proper nontrivial finalizer should be try {
do something; } finally { super.finalize(); } so your cleanup goes
first, then your superclass's -- reverse order of initialization; so
it shouldn't *start* with finalize. A proper clojure finalizer would
have to be (finalize [] (try (do something) (finally (proxy-super
finalize)))). Though finalizers are icky, they probably *should* be
allowed, for completeness' sake. I'm inclined to regard this as a bug,
unless someone posts with a convincing explanation why clojure proxies
having nontrivial finalizers would be a very bad idea.

> The fact that (as you note) the proxy code is documented not to have access 
> to protected members does suggest that the proxying class is at least not 
> straightforwardly a subclass of the base class.

As I understand it, there are two pieces to the proxy.

One is a subclass of the specified base class and/or interface(s). The
proxy object is of this class; as you saw, your HashMap derived proxy
had an (ancestors (.getClass x)) that contained HashMap among other
things.

The other component is a map of clojure function objects. The proxy
object's methods just look things up in this map and invoke them. The
map can be updated later, with update-proxy, and the existing proxy
object's methods will change behavior correspondingly, something not
otherwise possible. That's why there's a default call-super
implementation of every method, even if you don't override it when you
first call proxy, because if there wasn't, you couldn't later override
it with update-proxy.

It also lets you always call protected methods that you didn't override:

user=> (.toString (proxy [Object] [] (toString [] (do (.clone this) "boo!"))))
#<CloneNotSupportedException java.lang.CloneNotSupportedException:
user$java.lang.Object$0>

If the proxy didn't make clone public, the clojure function could not
call it in the do up above, because it's not a method of the proxy
class itself, but of its own class, as outlined above. And to prove
it:

user=> (.toString (proxy [Object] [] (toString [] (.getClassName
(first (.getStackTrace (Exception.)))))))
"user$eval940$fn__941"

The class user$eval940 is a class with a method that executes the
compiled (.toString ...) code. When that is entered at the REPL, it's
compiled, that class (or one with a similar name) is created, and that
method is then invoked to actually execute what you entered at the
REPL.

The class user$eval940$fn__941 is a nested class of that class, and
will be the body of the toString method specified for the proxy.

The proxy itself, as noted above, has a toString override that looks
up toString in some clojure map, pulls out an instance of
user$eval940$fn__941, and calls the no-argument invoke method, which
contains the compiled toString body.

So, the clone method of the proxy's class, user$java.lang.Object$0, is
being called from the class user$eval940$fn__941. In this case, it
would have worked anyway since both are in the default package, but if
they weren't in the same Clojure namespace (or both in single-segment
namespaces) it wouldn't work if the proxy class's clone method had
stayed protected instead of being made public. The scenario wheres
that would occur include:

Updating the proxy with update-proxy from a different namespace than
the proxy was created in.

The protected method is called by a helper function, which is called
from the proxy body, and the function and the proxy body are not in
the same namespace.


> The Joy of Clojure mentions (p196) that "Clojure doesn't encourage 
>implementation inheritance", that gen-class and proxy only
> provide "something like Java-style implementation inheritance", and  suggests 
> (p210) that proxy classes trade flexibility for
> performance.

Even then, a proxy method call is actually a Java call, a map lookup,
and another Java call, so that proxies can be updated after creation.
The newer reify is even more performant, but lacks this dynamic
updatability.

Proxy is plenty fast enough for a common use: GUI listener objects.
The slight added call overhead before your ActionListener code is
executing is nowhere near enough for you to have finished that mouse
click motion with your hand first, let alone made another click, or
keystroke, or whatever. :)

Anything called by a Java library in a tight loop via an interface,
though, you might prefer reify for. Reify has some other features,
too, and probably some other limitations. Though there's a sizable
overlap region where either can be used with acceptable results.

> But in that case, I'm stumped, and don't know how I would go about overriding 
>a finalize() method in an idiomatic and non-painful (ie
> non-gen-class) way.

Until and unless it proves to be a bug and gets fixed, it looks like
you'll need to use gen-class, or maybe even dirty your hands writing
actual Java source code. Sorry.

You might want to try reify and deftype first, though, before you give
up. One of those might let you override finalize.

There is, by the way, *at least* one bug here and possibly two.

If the proxy-can't-override-finalize behavior is unintentional, then
that's the bug.

On the other hand, if it *is* intentional, its undocumented nature is
one bug and its symptom being a silent failure rather than something
like #<CompilerException: cannot override finalize with proxy, use
gen-class/whatever instead> is another bug.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to