groovy-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From OC <...@ocs.cz>
Subject Re: Proxying how to?!?
Date Wed, 30 Mar 2016 16:19:41 GMT
Jochen,

thank you very much for quick and, as always, very knowledgeable response!

On 30. 3. 2016, at 17:12, Jochen Theodorou <blackdrag@gmx.org> wrote:
> There are multiple ways to "solve" the problem, but I am afraid, they will all look like
workarounds to you... The traditional Java way would be:
> interface InterfaceForAnyClassOfMine {
>  def getName()
> }
...

Well of course, but the problem is with 3rd-party classes, which do not implement such an
interface (and they might be even final, which prevents also all possible subclassing tricks
and actually _forces_ proxying).

And generally, it is 3rd-party classes one needs to proxy most time; one's own classes (which
I use here to quickly elucidate the gist of the problem) allow lots of other ways solve anything
which might proxy be needed for.

> Then of course you would not use AnyClassOfMine as base, but InterfaceForAnyClassOfMine.
You can make this a little bit more easy with @Delegate:
> class DumbProxy {
>  @Delegate InterfaceForAnyClassOfMine server
> }

I do not like this approach, for it does not cover dynamics of the server -- far as I understand
it properly, that is.

If the proxy forwards instead, i.e.,

===
class DumbProxy { def server; def methodMissing(name,args) { server."$name"(*args) } }
===

it covers even e.g., any functionality which has been added to server through its metaclass
at runtime.

On the other hand, I grant @Delegate one definite advantage (its methods would work when called
from Java, which methodMissing can't ever do, I am afraid), and one potential -- it might
be considerably faster[*].

> Using runtime traits I would come up with this version:
> class AnyClassOfMine { def name }
> trait DumbProxy {}
> def objects=[new AnyClassOfMine(name:"Proxied") as DumbProxy]
> for (DumbProxy o in objects) println "got $o.name"

This seems very interesting! I simply _must_ clean up my quagmire of ASTTs so that they work
with traits! (While I completely appreciate the strong reasons which led you to sort-of deprecate
ASTTs with traits, I must admit for me it was _very_ sad decision, which still, even today,
prevents me to use traits and forces me to stick with 3.8 :( )

> I think the basic problem is that you have no multiple inheritance in Java and only an
inconsitent fraction of it in Groovy.

Sort of. Myself, I would rather say the problem is basically in the Java terrible howler of
making static class/interface relationships overimportant.

ObjC does not have MI either, but it can be easily _simulated_ in there using message forwarding.
In Groovy (even in plain Java, with some boilerplate code essentially doing by hand what your
@Delegate ASTT does), it would work just as well, only if it were possible to typecast a DumbProxy
instance to AnyClassOfMine. There is no harm in it, for any method callable of AnyClassOfMine
is also callable of DumbProxy; alas, Java atrifically (and dumbly) closes that door by the
very “Cannot cast object” exception.

> So on the JVM I cannot make a class that implements a class AnyClassOfMine and DumbProxy
at the same time.

That is actually not needed at all. All we need is that the JVM calls AnyClassOfMine methods
on DumbProxy object. Which is problematic in pure Java (I understand one needs reflexion for
that, can be done, but is beyond ugly), but no problem at all in Groovy.

Groovy does allow that, and it works excellectly, if we stick with “def”. The problem
occurs when there is an existing API which not only _uses_ an instance of AnyClassOfMine,
but also _declares it_ as one. E.g.,

def gimmeObject { return flag?objectOfClassX:proxyToClassX } //[1]

works as-is without any glitch. Contrariwise

ClassX gimmeObject { return flag?objectOfClassX:proxyToClassX }

does not, only due to the blasted “Cannot cast object” exception; if it was not for that
thing, it would work just as well as [1] above.

> I can do such things with interfaces though. This again forces people to split their
classes in interfaces and implementations, both being first class citizens. Objective C is
much more flexible here.

I don't really think so. First, ObjC is actually more rigid in many places (e.g., it _forces_
you to split _any_ class to interface and implementation, willy-nilly :)), but the one (and
in this particular context I belive _only_ important) difference is that it freely lets you
cast anything to anything. The compiler, of course, behaves accordingly, trusting you you
had a good reason to do that typecast.

Of course, if you typecast, say, a Map to a String, the program would crash as soon as the
real Map gets first String's message it does not implement; but there's no potential problem
in that -- nobody sane would do such a typecast (and whomever would, serves him right).

On the other hand, it allows proxying. And more.

> But it means I cannot apply the same concept to the JVM world... not if I want to stay
with the Java class model.

That all said, I am afraid this still applies -- far as I understand, you not only can't make
“a class that implements a class AnyClassOfMine and DumbProxy at the same time” (which
is not needed), but you also cannot make a class which

(a) would implement DumbProxy; any method called on its instance would be interpreted normally
(b) at the same moment, would _look to_ the compiler _like_ AnyClassOfMine in the sense it
would call AnyClassOfMine's methods on its instances (and use AnyClassOfMine to resolve calls
which depend on argument type etc.)

Note that (b) would not _change_ the class anyhow; it would still stay a true-blue DumbProxy.
The only difference is that compiler would use it _as it were_ an AnyClassOfMine (which it
is not).

> ... But once you have two super classes you are lost... especially without the magic
-forwardingTargetForSelector:(SEL)sel { return self.server; }

I believe _this_ one magic you could implement in Groovy dispatcher, if you wanted, and pretty
easily at that. It is nothing but a quick-and-fast shortcut to “methodMissing(name,args)
{ target."$name"(*args) }”, which only forwards and does not pre-/post-process the method,
nothing else. Its purpose is to make it easier for programmer and faster runtime, but the
functionality is precisely the same[**].

Thanks and all the best,
OC

[*] On the other hand, the speed difference should not be that big if the forwarding is done
right, that is, at first forward the proxy itself installs the stub through its own metaclass.
Nevertheless, that's an optimization which works nicely (most time), but is not relevant to
the current debate.

[**] Actually in Groovy you would probably have to solve properties separately; ObjC has properties
too, but defines them solely through their accessors, which, I believe, is easier -- at least,
you don't need separate “methodMissing” and “propertyMissing” (twice) etc., for “methodMissing”
catches property accessors just as well as all other methods. But this, too, is completely
irrelevant to the current discussion :)


Mime
View raw message