Jochen,

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

On 30. 3. 2016, at 17:12, Jochen Theodorou <[email protected]> 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 :)

Reply via email to