Static Groovy - about calling methods
After a long time I am now here writing something in my blog again. In the past I talked mostly about new features and possibilities like for example http://blackdragsview.blogspot.com/2006/09/groovy-on-speed-fast-mode-for-groovy.html
I want to talk about a static mode for Groovy here, since this is a reoccurring thing on the lists. I get the feeling people don't understand exactly what I mean and I want to try and explain it here a bit more. Of course I have my knowledge and others have their knowledge and maybe I will tell something that is wrong here. Well, if that happens tell me, I am aware of the fact that I know only a small fraction. And maybe one of the readers here knows just the way I was searching for and unable to find.
One basic part in Groovy is the method call. Almost any operator results in a method call and many structures are simulated through method calls. Now Groovy is a language with what I call instance based multi methods. Let me give an example:
class A {
def foo(Object x){ bar(x) }
def bar(Object o){1}
}
class B extends A {
def bar(String s){2}
}
def a = new A()
assert a.bar("x") ==1
assert a.foo("x") ==1
def b = new B()
assert b.bar("x") ==2
assert b.foo("x") ==2
Now looking at that code you might realize, that Java would fail in the last assert for multiple reasons. First foo takes an Object typed parameter and that is never going to call a String based method, because the static type of the parameter is used as type for the argument, which then influences the method preselection process the compiler does. So Java would always select the bar(Object) method. The other aspect is that bar(String) is not declared in A, but in B. If you want another method get called in Java, you have to override one from the parent class, but bar(String) is not overriding in the Java sense of inheritance. So in Java you would in class B write a bar(Object) method, that checks if the given argument has the runtime type String and then dispatch to the String taking method else to super.bar(Object). In Groovy this is not needed. If bar is final, then in Java you cannot do that additional dispatch, for Groovy this makes no difference.
I should maybe also mention that often people talk about dynamic dispatch or dynamic method calls. What some mean is that from a given set of methods of a given class a method is selected using the runtime types of all involved arguments and the receiver. They often don't realize that this is multi methods. Coming from a Java thinking they assume only methods from class A can be taken, since foo is defined in A and so a bar of A must be used. In Java you might add that this is always true unless a subclass is overriding the method, but no new method signatures will be added to the set. Only, that if you use the dynamic type of the receiver anyway (yes, Java does this!) why make methods of it invisible? That was a decision made by the Java people and I am sure they had their reason to do so, but this is no requirement for a static language, especially if the return types cannot differ Before Generics were introduced this was the case for Java. Even for current Java I think it would be possible to ad. And if you take a look at MultiJava for example, you might realize that it does not have to be a dynamic feature either.
Another aspect of Groovy and method calls is the invokeMethod/methodMissing logic. Basically the method is called if the goal method couldn't be found. The parameters for this search are based on the dynamic types and multi methods, but a static language could have a methodMissing as well. But what would that mean?
A static mode for Groovy is something all those people like to have, that either think the compiler should do more checks or that Groovy should have more speed. Let us concentrate on compile time checks here. A common example is the misspelling of a method name or a method name gets changed. Let us take the example Chris Richardson in his Community One East 09 talk gave http://www.slideshare.net/chris.e.richardson/communityoneeast-09-dynamic-languages-the-next-big-thing-for-the-jvm-or-an-evolutionary-dead-end On slide 30 executeRequest got renamed to executeEc2Request and he complains about his test not picking up the problem, where it should have been a job of the compiler anyway.
Now let us imagine a language in which every method call, that does not point to a specific method, the compiler will create a call to method missing. In such a language a change of the signature of one method will most probably not lead to a compilation error. Instead the method call will now call method missing. In other words: One of the most important features, the check of a method call, will not work.
Still it wouldn't be worthless since in refactorings you could still have the method being changed automatically you might say. But this is an IDE-thing and not a compiler thing. As such it is only partially concern of the language and more of the IDE being able to do something in such cases.
class A {Let us assume this is the code in your IDE and you want to refactor A.bar into A.foo. An IDE could through type inference know that "a" will be of type A and as such know that the call a.bar() would normally have called A.bar and that if I rename that method, the IDE should change a.bar() into a.foo(). Also if the example would have been
def bar() {1}
}
def a = new A()
a.bar()
class A {the IDE would have had a much tougher job. But an IDE knows all the code that is involved, so it could try to identify all types that are used to call exec and then distill a common super type for it. So in the end it could still find out, that exec is called with A and since we refactor A.foo it could still find that it should be changed to A.bar. It is difficult, but not impossible for the IDE to set things right here as we can resolve the issue with just type inference. Let us go even one step further
def bar() {1}
}
def exec(x){x.bar()}
def a = new A()
exec(a)
class A {Here A and B have no common super type that contains the definition of bar, still a name change for A.bar would affect the program. The right thing here to do would be to also change the name of B.bar. If the IDE knows how exec is called and with what types these calls are made, it can find this out. There might be cases in which the IDE won't be able to find the right types, but I am positive that in most cases the IDE will be able to just do that. If such type inference is available, then auto completion for method signatures will work too. So the biggest use cases in an IDE, renaming and completion, can work out just fine in most cases even with Groovy.
def bar() {1}
}
class B {
def bar() {2]
}
def exec(x){x.bar()}
def a = new A()
exec(a)
def b = new B()
exec(b)
So if method missing has no positive affect and multi methods are restricted, then what is left is the normal method call logic Java uses. And the question here is if that is worth it.
6 comments:
In your first example, i think you meant:
...
def b = new B()
assert b.bar("x") ==2
assert b.foo("x") ==2
Note: the assertion on foo/bar methods of instance "b" (you had instance "a")
thanks, I corrected the error
This is basically what Duby does. Duby is statically-typed but uses intference to reduce the amount of type declarations. For now only "normal" static types are supported, but I would like to add structural types. In that case you would specify (or the compiler would infer by looking at the code) which methods are expected to be available on the incoming object. Since it should be able to statically determine all values passed to that method, it should be able to make such calls dispatch directly.
I think in Groovy there are too many problems to make static compilation possible. It ignores visibility. It ignores various rules of Java dispatch and overriding like the like you described. It allows replacing the methods on a target type at any time. It allows replacing the entire *metaclass* on a target type at any time. It allows thread-specific changes to method lookup for a given class at any time. Unless you turned off most of these features, you would not be able to statically compile almost any code safely.
But perhaps I don't really understand what you mean?
"Another aspect of Groovy and method calls is the invokeMethod/methodMissing logic."
I am thinking of this, although it's just a partial answer anyway. Possibly in a static language, we may declare Grails' findBy* with something like:
class C {
def findBy/s/(Object ...) {
}
}
Where "s" will be of type String and can be used as an argument in this method.
I think methodMissing could be modelled in the same way. But as you questioned, they will be a dynamic feature of a static language.
@Charles Oliver Nutter
What I mean with a structual typing here is, that if you for example have a method:
def foo(x){
return x*x
}
then you will need support for the * operator. The type for x is then anything that supports that operation. If mapped to method mul for example x would have a pseudo interface interface x_Type<U,V> {
V mul(U u, U u);
}
of course generics have limits, so at one point you need something more powerful, but this is just an example to show the principle. Anyway, foo(y) is then valid not if it implements the interface x_Type, no, it is valid if the type of y allows all operations that x_Type. For example if y has a class
class y_Class {
int mul(int a, int b){
return a*b;
}
int plus(int a, int b){
return a+b;
}
}
then x_Type requires a subset of the operations that y_Class provides. As a result the method call is compilable. Maybe I should name that requirement typing, since structual typing is not really specified.
The problem of this way of typing is, that it has not much to do with how the Java does typing. But if you are required to leave the Java world of types, direct method calls become more and more difficult. Maybe giving each interface it own interface would work. Only calling normal Java code would then still be a problem. Well, I guess it is nothing you don't know from JRuby already.
That Groovy ignores visibility is in that general meaning not true. private methods for example are not visible in sub classes. But regarding overriding rules you are of course right. Still those rules have nothing to do with not being possible in a static way. The problem with static multi method systems is, that you will need to know all involved code. Since the JVM does allow for adding classes dynamically this requirement becomes a small problem. But as MultiJava shows, there are ways around that.static compilation is much more a problem when you still want runtime meta programing, especially in that extend that Groovy does allow. So here we agree again.
I made this post (more will follow) mostly because I want to show people a little bit of the consequences a static Groovy would have.
@chanwit
your findBy/s/ idea is interesting. The problem with missing method I see is, that there is no name prefix we could use.
Post a Comment