Coming from Java to Groovy and seeing that Groovy looks like Java with sugar, you are tempted to write code like this:
private String take(List list) { return 'a list' } private String take(String s) { return 'a string' }
But when you call this method take with null you get strange results:
public void testDispatch() { String s = null assertEquals('a string', take(s)) // fails! }
It fails because Groovy does not use the declared types. Since it is a dynamic typed language it uses the runtime type which is NullObject and calls the first found method!
So when using your old Java style to write code in Groovy beware that you are writing in a dynamic environment!
Lesson learned: learn the language, don’t assume it behaves in the same way like a language you know even when the syntax looks (almost) the same.
This is one of the reasons because I don’t like dynamic languages too mucho. However, I think that method overloading is not good neither (because of overlading, method parameters can’t be contravariant when inheritinh in Java), but with Groovy you could “accidentally” overload a method and get this kind of behavior.
In my opinion, it’s just a bug. You should post an email on groovy-dev mailing list to be sure.
Rémi
Remi,
I don’t think this is a bug. In some areas Groovy uses a different semantic than Java. And since Groovy is a dynamic language it uses the runtime type information. Null has a type of NullObject and so it loads the first method.
The real question is why Groovy don’t use the static type info when the user provide it.
I agree that Groovy is a dynamic language, so the dispatch is a multi dispatch (i.e not a Java dispatch) but the way Groovy handle null is wrong, it should use the type declared by the user.
Rémi
Remi,
I agree with you here, Groovy should use the static type info when provided. I once read that only insert a type check and do not use the type further.
So actually inserting types in Groovy is bad in two ways: performance and expectation.
Anyhow, looks like a pretty good source of easy to commit and hard to find errors. Do you mean it’s enough to change the order of the methods and the test passes?
If I’d designed groovy I’d probably treat this situation as an exceptional one, otherwise I’d try to warn the client about the arbitrary decision that is taken when choosing one of those two perfectly matching methods.
Marky,
the test passes when you change the method order but I don’t think this helps. I think type declarations in Groovy can be dangerous in certain situations where you would expect another outcome because of the types.
Method dispatch is one of the most important differences to understand between java and groovy. it is worth the time for new groovy users to spend some time learning it.
Definitely.
While it’s true that it uses the runtime arguments, it doesn’t look like it picks the first method it comes to (at least with Groovy 1.7.10). It appears to look for the “most appropriate” method. At least, in my testing, it consistently picked the least specific method argument.
So, no matter how I wrote it, it always picked the List method. However, when I added a version of take that has Object as the argument, it always used that method, even when I re-arranged the arguments.
So, it’s still very true that you need to understand the method dispatch, and the difference between static and dynamic dispatch, but the results at least seem to be consistent and testable.
It’s too bad about the null issue. It’s the only major problem area I see (and I’d argue that for all other instances, Groovy’s dynamic method dispatch is more “appropriate” – so that the most specific method is what is always called).
Finally, as I found on Groovy Goodness, there’s a trick to ensuring that the method you want is called:
// we may be looking at null values, so force the correct method call
assertEquals('a string', take(s as String))