Monday, May 05, 2008

Groovy Type Handling is Broken

Here's a very simple Java program snippet:

List<Integer> list = new ArrayList<Integer>();
Collection<Integer> col = list;
list.add( 2 );
list.add( 1 );
list.add( 0 );
System.out.println( list );
col.remove( 0 );

Run this and you'll get what you'd expect. Before using the col reference to remove the integer/Integer with value 0 (zero), you see:

[2, 1, 0]

Then, after calling remove on the collection, we see the integer/Integer with value zero is gone (that's the one we said to remove):

[2, 1]

Groovy says that it allows you to use types and will honor them at runtime. But it doesn't.

Take the same snippet of code and run it as a Groovy script. Remember that the Collection interface has a remove method, and that method expects you to pass it an object which it will remove from the collection. So we would expect to see the same result as in Java. But we don't. Instead, we get this:

[2, 1, 0]
[1, 0]

Groovy has removed the Integer with a value of 2! It did this because of Groovy's 'multi-methods'. Groovy looked at the real type of col, which is an ArrayList. It then found a remove method that takes an integer/Integer. That method removes the Nth item from the list. It used that method and removed the zero-th item (the one with a value of 2).

But remember, I invoked the remove. method through the Collection interface. The collection interface does not have a method taking an integer/Integer but it does have a remove method taking the object to be removed!

This is a truly awful result. It removes our ability to reason about how code will behave by looking at the types involved. In order to know how this will behave, I cannot make any use of the type information; in fact the type information is downright misleading. Even worse, as the type system evolves and new classes are introduced, new methods with entirely different signatures may get invoked.

This approach to handling types is essentially broken. The claim that Groovy honors types (even the weakened form of honoring it by doing casts at runtime) is flat out wrong.

Groovy needs to either start honoring type information, or get rid of it.