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.