[groovy-jsr] The MetaClass and the MetaObjectProtocol

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

[groovy-jsr] The MetaClass and the MetaObjectProtocol

tugwilson
I think some of our difficulties arise from inadequacies on the  
current MOP as implemented by the MetaClass.

I am firmly of the view that all property, attribute and method calls  
should go via the MetaClass (Note: this is different from saying that  
the MetaClass should make all the calls or get/set values on  
properties or attributes - see later for details).

There are good reasons for everything to go via the MetaClass - we  
already have examples of MetaClass interceptors which perform useful  
work (logging or benchmarking). There are also valid uses for hooking  
into the MetaClass to provide useful behaviour (GPath or Builders)

The problem is that we have overloaded several jobs on the same  
method call in the MOP which the MetaClass implements.

For the rest of this piece I will talk only about invokeMethod as  
it's the most complex case - similar arguments apply to the other  
methods.

There are three main cases when calls to invokeMethod are generated  
by the compiler:

1/ when the method is called via this (explicitly or implicitly)

2/ when the method is called via super

3/ when the method is called via some other reference to an object

Currently we use the same method to make all these calls. I think  
that is a mistake and that this mistake is the source of much of our  
trouble and confusion.

This is what I think the three versions of these methods should do:

Call via this: invokeThisMethod()

The default MetaClass would select the method to be called from all  
the methods on the class (public, protected and visible private) and  
any method added by DGM or any other mechanism. If the method  
selected was public then it would call it and return the value (or  
null if the method was void). If the method was protected or private  
then it would just return a unique globally known value (called  
NOT_CALLED, for example). If no public, protected or private method  
could be found to match the name and the parameters passed then an  
exception would be thrown.
The compiler would generate code which called this method, examined  
the result and did a direct local call if the returned value was  
NOT_CALLED. It would have to generate code which selected overloaded  
methods, of course.


Call via: super invokeSuperMethod()

The default MetaClass would just return NOT_CALLED if there was a  
super method which matched the name and the parameters passed and  
throw an exception otherwise.
The compiler would generate code which was the same as for invoking  
via this except that the call was made via super.

Call via other reference: invokeMethod()

The default MetaClass would select the method from the public methods  
and from any methods added by DGM or any other mechanism. If a method  
matched it would call it otherwise it would throw an exception.
The compiler would just generate a call to invokeMethod on the  
MetaClass.


This would have the following advantages:

The programmer would have complete and fine grained control over the  
behaviour of any class or any instance of a GroovyClass via custom  
metaclasses. The external behaviour can be controlled whilst leaving  
the internal behaviour unchanged (as is required for GPath) or the  
internal behaviour could be changed whilst leaving the external  
behaviour unchanged (as id required for Builders)

MetaClass Interceptors would see all the calls an property/attribute  
access

Not dodgy reflection tricks would be needed. All calls could be made  
directly (the compiler will compile direct calls and the MetaClass  
could generate bytecode to make the calls directly for public methods  
- which it does now).

Performance would improve dramatically for private and protected access.

We would be able to implement access rules almost exactly the same as  
the Java rules.

I hope I haven't missed any glaring difficulty with this scheme - if  
I have I'm sure it will be pointed out;)

Note that this suggestion does not have a direct baring on scoping  
rules: I'd like to see if this scheme has any general support before  
trying to address scope rules



John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

RE: [groovy-jsr] The MetaClass and the MetaObjectProtocol

Dierk König
This proposal makes a lot of sense to me. Thanks a lot, John!
I'm not sure the class generator has enough information to
generate the method calls as needed in this proposal -
(calls inside a builder closure not as invokeThisMethod, etc.)
but I'll leave that to the experts...

Mittie

> -----Original Message-----
> From: John Wilson [mailto:[hidden email]]
> Sent: Donnerstag, 17. November 2005 11:43
> To: [hidden email]
> Subject: [groovy-jsr] The MetaClass and the MetaObjectProtocol
>
>
> I think some of our difficulties arise from inadequacies on the  
> current MOP as implemented by the MetaClass.
>
> I am firmly of the view that all property, attribute and method calls  
> should go via the MetaClass (Note: this is different from saying that  
> the MetaClass should make all the calls or get/set values on  
> properties or attributes - see later for details).
>
> There are good reasons for everything to go via the MetaClass - we  
> already have examples of MetaClass interceptors which perform useful  
> work (logging or benchmarking). There are also valid uses for hooking  
> into the MetaClass to provide useful behaviour (GPath or Builders)
>
> The problem is that we have overloaded several jobs on the same  
> method call in the MOP which the MetaClass implements.
>
> For the rest of this piece I will talk only about invokeMethod as  
> it's the most complex case - similar arguments apply to the other  
> methods.
>
> There are three main cases when calls to invokeMethod are generated  
> by the compiler:
>
> 1/ when the method is called via this (explicitly or implicitly)
>
> 2/ when the method is called via super
>
> 3/ when the method is called via some other reference to an object
>
> Currently we use the same method to make all these calls. I think  
> that is a mistake and that this mistake is the source of much of our  
> trouble and confusion.
>
> This is what I think the three versions of these methods should do:
>
> Call via this: invokeThisMethod()
>
> The default MetaClass would select the method to be called from all  
> the methods on the class (public, protected and visible private) and  
> any method added by DGM or any other mechanism. If the method  
> selected was public then it would call it and return the value (or  
> null if the method was void). If the method was protected or private  
> then it would just return a unique globally known value (called  
> NOT_CALLED, for example). If no public, protected or private method  
> could be found to match the name and the parameters passed then an  
> exception would be thrown.
> The compiler would generate code which called this method, examined  
> the result and did a direct local call if the returned value was  
> NOT_CALLED. It would have to generate code which selected overloaded  
> methods, of course.
>
>
> Call via: super invokeSuperMethod()
>
> The default MetaClass would just return NOT_CALLED if there was a  
> super method which matched the name and the parameters passed and  
> throw an exception otherwise.
> The compiler would generate code which was the same as for invoking  
> via this except that the call was made via super.
>
> Call via other reference: invokeMethod()
>
> The default MetaClass would select the method from the public methods  
> and from any methods added by DGM or any other mechanism. If a method  
> matched it would call it otherwise it would throw an exception.
> The compiler would just generate a call to invokeMethod on the  
> MetaClass.
>
>
> This would have the following advantages:
>
> The programmer would have complete and fine grained control over the  
> behaviour of any class or any instance of a GroovyClass via custom  
> metaclasses. The external behaviour can be controlled whilst leaving  
> the internal behaviour unchanged (as is required for GPath) or the  
> internal behaviour could be changed whilst leaving the external  
> behaviour unchanged (as id required for Builders)
>
> MetaClass Interceptors would see all the calls an property/attribute  
> access
>
> Not dodgy reflection tricks would be needed. All calls could be made  
> directly (the compiler will compile direct calls and the MetaClass  
> could generate bytecode to make the calls directly for public methods  
> - which it does now).
>
> Performance would improve dramatically for private and protected access.
>
> We would be able to implement access rules almost exactly the same as  
> the Java rules.
>
> I hope I haven't missed any glaring difficulty with this scheme - if  
> I have I'm sure it will be pointed out;)
>
> Note that this suggestion does not have a direct baring on scoping  
> rules: I'd like to see if this scheme has any general support before  
> trying to address scope rules
>
>
>
> John Wilson
> The Wilson Partnership
> http://www.wilson.co.uk
>
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-jsr] The MetaClass and the MetaObjectProtocol

Guillaume Laforge
Administrator
In reply to this post by tugwilson
Hi John,

This proposition is interesting... However, I have a few questions,
and I'd like a few points to be clarified. Let's start!

1) Does it mean the MOP contract of GroovyObject will be changed? Or
will the exact same set of methods be available (invokeMethod,
get/setProperty)? Or will there also be invokeSuperMethod,
invokeThisMethod that ppl can override themselves?

2) How does it behave with regards to GroovyInterceptable? It looks to
me like by default, everything will be interceptable, even toString(),
which means that the new behaviour will be like GroovyInterceptable.

3) (related to #2) When calling a method (like say foo()) inside the
code of the class (not used from an outside class), will it call
invokeMethod()? Because the current problem with GroovyInterceptable
is that when you call any method, they'll be caugth by invokeMethod(),
which also means that all methods calls *inside* invokeMethod() will
get caught too, resulting in an infinite loop. So how does your
proposition behaves with regard to whether we're inside the definition
the class, our outside while using the class?

4) Currently, super.foo() will not call super's invokeMethod() but
will just call statically typed methods. Should invokeSuperMethod()
call super's invokeMethod()?

Those questions apart, this proposition is interesting and makes calls
pretty efficient -- more than it is today with protected/private
methods.

--
Guillaume Laforge
http://glaforge.free.fr/blog/groovy
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-jsr] The MetaClass and the MetaObjectProtocol

tugwilson

On 17 Nov 2005, at 12:38, Guillaume Laforge wrote:

> Hi John,
>
> This proposition is interesting... However, I have a few questions,
> and I'd like a few points to be clarified. Let's start!
>
> 1) Does it mean the MOP contract of GroovyObject will be changed? Or
> will the exact same set of methods be available (invokeMethod,
> get/setProperty)? Or will there also be invokeSuperMethod,
> invokeThisMethod that ppl can override themselves?

This doesn't require these methods to be changed.

The existing methods on GroovyObject allow the programmer to specify  
dynamic behaviour simply but without fine grained control (which is  
what 99% of people will want to do). If you want finer grain control  
then you need to cathc the calls to the metaclass. The existing  
GroovyObject calls let you do that (see  
grovvy.util.slurpersupport.GPathResult for an example of this.
>
> 2) How does it behave with regards to GroovyInterceptable? It looks to
> me like by default, everything will be interceptable, even toString(),
> which means that the new behaviour will be like GroovyInterceptable.

GroovyInterceptable controls how calls are made to the GroovyObject  
methods. It has no effect on the calls to MetaClass. This proposal  
does not require any change to that behaviour. GroovyInterceptable is  
about scoping rules this proposal does not talk about scoping rules.

>
> 3) (related to #2) When calling a method (like say foo()) inside the
> code of the class (not used from an outside class), will it call
> invokeMethod()? Because the current problem with GroovyInterceptable
> is that when you call any method, they'll be caugth by invokeMethod(),
> which also means that all methods calls *inside* invokeMethod() will
> get caught too, resulting in an infinite loop. So how does your
> proposition behaves with regard to whether we're inside the definition
> the class, our outside while using the class?

This proposal does not address that question - I would like to  
discuss that after we have decided if this is a good idea or not:)

What I'm suggesting here is that we give the MetaClass information  
about the context in which the call is being made and then let the  
MetaClass implement the language rules for that context. I'm not, for  
the moment, suggesting what those language  rules should be.

However, since you ask..

I would expect the GroovyObject methods only to be called via  
invokeMethod. I would expect to dump GroovyInterceptable. I would  
expect things like Builders to hook into the MetaClass calls and  
catch the invokeThisMethod() call.

>
> 4) Currently, super.foo() will not call super's invokeMethod() but
> will just call statically typed methods. Should invokeSuperMethod()
> call super's invokeMethod()?

super is quite a nasty problem. It can only be done bytecodes running  
in the subclass. It can't be done via reflection.

I was assuming that the MetaClass would never want to do anything but  
inspect the super call. We could change that an allow the call to  
callSuperMethod to return a result ot NOT_CALLED so that it could  
suppress the call via generated bytecodes and to do something else.  
This might be preferable.



John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

Re: [groovy-jsr] The MetaClass and the MetaObjectProtocol

Guillaume Laforge
Administrator
On 17/11/05, John Wilson <[hidden email]> wrote:
> On 17 Nov 2005, at 12:38, Guillaume Laforge wrote:
> > 1) Does it mean the MOP contract of GroovyObject will be changed? Or
> > will the exact same set of methods be available (invokeMethod,
> > get/setProperty)? Or will there also be invokeSuperMethod,
> > invokeThisMethod that ppl can override themselves?
>
> This doesn't require these methods to be changed.

Which is a good thing for backward compatibility... since we know that
concern is real.

> The existing methods on GroovyObject allow the programmer to specify
> dynamic behaviour simply but without fine grained control (which is
> what 99% of people will want to do). If you want finer grain control
> then you need to cathc the calls to the metaclass. The existing
> GroovyObject calls let you do that (see
> grovvy.util.slurpersupport.GPathResult for an example of this.

When you say catching calls to the metaclass, you mean using a custom
metaclass and overriding invokeMethod() in the metaclass not in
GroovyObject?
And MetaClass (or the custom one provided) would delegate
invokeMethod() to the GroovyObject's redefined invokeMethod()?

I think you need to put those MetaClass changes into the big picture
of GO + MC. Because I'm not sure what the changes are regarding their
interaction.

> > 2) How does it behave with regards to GroovyInterceptable? It looks to
> > me like by default, everything will be interceptable, even toString(),
> > which means that the new behaviour will be like GroovyInterceptable.
>
> GroovyInterceptable controls how calls are made to the GroovyObject
> methods. It has no effect on the calls to MetaClass. This proposal
> does not require any change to that behaviour. GroovyInterceptable is
> about scoping rules this proposal does not talk about scoping rules.

Currently, without GI, statically typed methods are prefered over
dynamic pseudo-methods of GO#invokeMethod(). This means that calls to
toString() can't be intercepted. With GI, all calls are intercepted
and routed through GO#invokeMethod() but that unfortunately means that
all calls from within invokeMethod() are triggering infinite loops --
which is bad. And I was wondering whether this approach and those
changes to MetaClass would helps solve that by allowing statically
typed methods be called preferably to pseudo-methods when the call is
made inside the class itself.

And to be frank, if that were possible, I think we should indeed ditch
GI and make it the default behaviour! Since the calls inside the class
would be able to escape the invokeMethod() loop...


> > 3) (related to #2) When calling a method (like say foo()) inside the
> > code of the class (not used from an outside class), will it call
> > invokeMethod()? Because the current problem with GroovyInterceptable
> > is that when you call any method, they'll be caugth by invokeMethod(),
> > which also means that all methods calls *inside* invokeMethod() will
> > get caught too, resulting in an infinite loop. So how does your
> > proposition behaves with regard to whether we're inside the definition
> > the class, our outside while using the class?
>
> This proposal does not address that question - I would like to
> discuss that after we have decided if this is a good idea or not:)

That's why I need the bigger picture with the interaction of GO, GI and MC :-)
But this proposition is interesting, and certainly is a good idea.
However, we might have to see if that fits with Jochen's proposition
as well, or if that can be considered orthogonal or not.

> What I'm suggesting here is that we give the MetaClass information
> about the context in which the call is being made and then let the
> MetaClass implement the language rules for that context. I'm not, for
> the moment, suggesting what those language  rules should be.

This behaviour and differenciation is a good thing IMHO.

> However, since you ask..
>
> I would expect the GroovyObject methods only to be called via
> invokeMethod. I would expect to dump GroovyInterceptable. I would
> expect things like Builders to hook into the MetaClass calls and
> catch the invokeThisMethod() call.

That sounds reasonable and I hope it's feasable and doesn't complicate
the implementation of builders.

> > 4) Currently, super.foo() will not call super's invokeMethod() but
> > will just call statically typed methods. Should invokeSuperMethod()
> > call super's invokeMethod()?
>
> super is quite a nasty problem. It can only be done bytecodes running
> in the subclass. It can't be done via reflection.
>
> I was assuming that the MetaClass would never want to do anything but
> inspect the super call. We could change that an allow the call to
> callSuperMethod to return a result ot NOT_CALLED so that it could
> suppress the call via generated bytecodes and to do something else.
> This might be preferable.

Right, that's certainly preferable.

--
Guillaume Laforge
http://glaforge.free.fr/blog/groovy
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-jsr] The MetaClass and the MetaObjectProtocol

tugwilson

On 17 Nov 2005, at 13:34, Guillaume Laforge wrote:

>> The existing methods on GroovyObject allow the programmer to specify
>> dynamic behaviour simply but without fine grained control (which is
>> what 99% of people will want to do). If you want finer grain control
>> then you need to cathc the calls to the metaclass. The existing
>> GroovyObject calls let you do that (see
>> grovvy.util.slurpersupport.GPathResult for an example of this.
>
> When you say catching calls to the metaclass, you mean using a custom
> metaclass and overriding invokeMethod() in the metaclass not in
> GroovyObject?

Yes, that's what the example I pointed you at does. There's a helper  
class groovy.lang.DelegatingMetaClass which you sublcass and then  
catch the method calls you want to intercept.

> And MetaClass (or the custom one provided) would delegate
> invokeMethod() to the GroovyObject's redefined invokeMethod()?

That decision is not what this proposal is about.

I think that only the invokeMethod method on MetaClass would delegate  
the call to the GO's invokeMethod but htis proposal does not force  
this to happen nor does it prevent it.

>
> I think you need to put those MetaClass changes into the big picture
> of GO + MC. Because I'm not sure what the changes are regarding their
> interaction.

This proposal doesn't require or preclude and changes to the "big  
Picture" it's just a change to the runtime system's "plumbing" which  
allows us to implement any sane name resolution system we choose to.

>
>>> 2) How does it behave with regards to GroovyInterceptable? It  
>>> looks to
>>> me like by default, everything will be interceptable, even  
>>> toString(),
>>> which means that the new behaviour will be like GroovyInterceptable.
>>
>> GroovyInterceptable controls how calls are made to the GroovyObject
>> methods. It has no effect on the calls to MetaClass. This proposal
>> does not require any change to that behaviour. GroovyInterceptable is
>> about scoping rules this proposal does not talk about scoping rules.
>
> Currently, without GI, statically typed methods are prefered over
> dynamic pseudo-methods of GO#invokeMethod(). This means that calls to
> toString() can't be intercepted. With GI, all calls are intercepted
> and routed through GO#invokeMethod() but that unfortunately means that
> all calls from within invokeMethod() are triggering infinite loops --
> which is bad. And I was wondering whether this approach and those
> changes to MetaClass would helps solve that by allowing statically
> typed methods be called preferably to pseudo-methods when the call is
> made inside the class itself.
>
> And to be frank, if that were possible, I think we should indeed ditch
> GI and make it the default behaviour! Since the calls inside the class
> would be able to escape the invokeMethod() loop...

I *think* the mechanism I'm proposing helps with this.

>
>
>>> 3) (related to #2) When calling a method (like say foo()) inside the
>>> code of the class (not used from an outside class), will it call
>>> invokeMethod()? Because the current problem with GroovyInterceptable
>>> is that when you call any method, they'll be caugth by  
>>> invokeMethod(),
>>> which also means that all methods calls *inside* invokeMethod() will
>>> get caught too, resulting in an infinite loop. So how does your
>>> proposition behaves with regard to whether we're inside the  
>>> definition
>>> the class, our outside while using the class?
>>
>> This proposal does not address that question - I would like to
>> discuss that after we have decided if this is a good idea or not:)
>
> That's why I need the bigger picture with the interaction of GO, GI  
> and MC :-)
> But this proposition is interesting, and certainly is a good idea.
> However, we might have to see if that fits with Jochen's proposition
> as well, or if that can be considered orthogonal or not.

as I have said before this proposal doesn't force is towards any  
particular decision on how to deal with name resolution si we are not  
really discussion how to address the bigger picture.

>
>> What I'm suggesting here is that we give the MetaClass information
>> about the context in which the call is being made and then let the
>> MetaClass implement the language rules for that context. I'm not, for
>> the moment, suggesting what those language  rules should be.
>
> This behaviour and differenciation is a good thing IMHO.
>
>> However, since you ask..
>>
>> I would expect the GroovyObject methods only to be called via
>> invokeMethod. I would expect to dump GroovyInterceptable. I would
>> expect things like Builders to hook into the MetaClass calls and
>> catch the invokeThisMethod() call.
>
> That sounds reasonable and I hope it's feasable and doesn't complicate
> the implementation of builders.

No I think it makes builders very easy to do.




John Wilson
The Wilson Partnership
http://www.wilson.co.uk