Monday, November 12, 2007

The Ugly Duckling of Java!

It took me time to find a good title. It could have been things like:
  • What's so ugly about explicit type parameters?
  • Done with inference!
  • Let's pave the way for reification and promote explicit type parameters!
Chaotic java already made a similar point: Can someone please explain type inference to me?.

But all these compiler language terms, are showing off for no reason. The problem is not so complicated, but the stakes are high. Like Eric Burke says in his blog A Syntax Trick I Was Not Aware Of, there is a syntax in Java that very few people use and encounter: Explicit type arguments or parameters. One of the rare places where you may have encountered it, is in the JLS and javac code and test code. So, it looks like:
List<String> empty = Collections.<String>emptyList();
The first reaction, for most Java developers, is: WTF?!

But I remember the first time I started converting some 1.4 code to use generics and wrote:
Map<Integer, Map<String,Thing>> messages;
Wow, that's a lot of <>! And when I saw the amount of bugs the compiler gave me (wrong casting and objects in put()), I really thanked the <>, and asked for more ;-)

Why, the Java compiler wants to hide <> of explicit type parameter?
On IntelliJ the <String> before the emptyList method, is underlined with the remark: Explicit type arguments can be inferred. Basically, it means the compiler can be smart enough to replace the content of <> with the correct type. Great, at first. But wait?
Like Stephan says in Explicit Static Types are not for the Compiler, but for the Developer - Duh, the compiler knows a lot more stuff. He can remove a lot of all this types we are writing.
I love strongly type Java, and all the types repetition. I love the types in the code because it's more readable.

Question: Why explicit type parameters are an exception?
Answer: It got the Ugly Duckling stamp for some reason! Some said that it was dangerous for kittens!

Personally, I think there are 2 reasons:
- When Java 5 came out it looked like too much <> all over and Sun tried to remove some unneeded ones.
- It lacks consistency. I would really like to know, why explicit type parameters are declared BEFORE the method name? When, in constructor and type declaration, they are declared AFTER. In the later, they are enforced, and everybody is using them!

Another worrying inconsistency that may appear in Java 7 is around the "Short instance creation" issue. For example the nicest proposal so far is Neal's constructor type inference. I really like this proposal because it enables inference to work, but you SEE it working. It's not hidden woodoo compiler stuff. According to this proposal instead of:
List<String> list = new ArrayList<String>();
you'd write:
List<String> list = new ArrayList<>();

Again, a strange sense of peculiarity for the method emptyList. In the type inference block of Alex Miller Java 7 page, you see the peculiar treatment of methods compare to types.
Why nobody wants:
List<String> empty = Collections.<>emptyList();
It's the same, no? Let's be consistent!

Now, the main big issue that I have with this inference vs. explicit issue, is that it's moving Java away from reification of generics. It's going in the wrong direction. I just refactored some JPA code from:
// The Class of the bean implementaion provided by
// the framework and unknown to the client
Class implClass = ...;
MyBeanInterface b = (MyBeanInterface) em.find(implClass,pk);
to
MyBeanInterface b = em.<MyBeanInterface>find(implClass,pk);
It may be just changing casting to generics. But that's the point of generics being way nicer and more powerful.

The other point is, when generics will be reified I'd be closer to the perfect methods:
MyBeanInterface b = em.<MyBeanInterface>find(pk);
or with VISIBLE type inference
MyBeanInterface b = em.<>find(pk);
These methods will find the implementation from the interface. Exactly what I need, the client code doesn't know about the implementation class, and the code is very clean and readable.

Today, because of erasure, in most methods (80%) that use generic types, you end up having Class<T> or Collection<T> as a parameter. So, inference works most of the time, and it's saving us from having to visualize the Ugly Duckling. For sure, it worked.

Now, for the 20% that still resist the roman empire of javac guru inference power, a new wave is coming. They want to write a "smarter" javac, that will make inference work (Check Kevin Bourrillion and Bob Lee comments here).

Here, there is a shift in the javac thinking, and it's going against readability, since: The "smarter" (or magical) the compiler is, the less "readable" your code is. Furthermore, inference will never be (by definition) 100% sure. So, why bother? The final code is more readable anyway...

IMHO: The kittens are safe, and reifying generics is more important than inference.

5 comments:

avah said...

Hmm... I didn't really connect the two in my own post but I agree with you ( and I think many will ): I'd prefer to write even another 50% of triangle brackets if it would be more useful for type-detection later than remove all of them and keep to the current situation, where generics are just sugar-coating for casting.

But why do you think type inference will stop that approach? I can see another thing you're saying which is: because we want to reach a place where generics mean something, we'll never get inference right, so why bother. So, I guess I'm just trying to understand the message here for my own mind.. :)

Brian said...

It's all part of the worrisome trend towards way too many annotations. My favorite example is the @Overrides annotation. It used to be that the IDE would display an "overrides" annotation in the gutter and I was happy with that. Now we're supposed to put @Overrides on the method as well? (Of course, I know the reason; this actually gives us an extra compile warning when the code changes.)

There is no similar excuse for explicit declarations for types that can easily be inferred. The IDE can still add an automatic annotation telling you the type whenever you want to know. But we shouldn't have to clutter our code with these annotations all the time, except at the places where two different classes have to agree and it's reasonable to do a consistency check between separately written code.

Ricky Clarkson said...

I always like to think about how things would look if you took them to an extreme. It helps me to make a decision quickly, even if I get it wrong sometimes..

int i=Math.max(x+10,y), if we were as explicit as possible with types, could look like:

int i=Math.max(int,int)(x(int)+10,y(int));

We don't feel the need to qualify overloaded methods with their types (as in Math.max(int,int) above), or to repeat the types of variables past their first declaration.

Similarly, int i=10; byte b=5; not int i=10(int); byte b=5(byte);

(The above (int) and (byte)s are not meant to be typecasts, but specifications. That's why they're not on the left.)

Why, therefore, do we need to declare the type parameter of a list more than once?

List<String> list=new ArrayList<>(); should be sufficient. In fact, you can get rid of <> there and it works fine in Java 5 (with a warning). If Java 7 adds reification, it probably won't break this example.

Let's take it to the other extreme - you could make the compiler's inference really really powerful, and then you would only have to specify some (possibly not any) types. You could still query your IDE or use reflection to tell you the types of things.

And to answer a question from your post - explicit type parameters for methods are declared after the dot to distinguish them from the less-than operator. In the example of call(var.name<type,another>()), you have to read quite far ahead (as far as >) to see that this is explicit type parameters, and not the less-than operator.

Bryan said...

Brian wrote: "we shouldn't have to clutter our code with these annotations all the time". Who's forcing you?

Dhruv Gairola said...

Shouldn't "moving Java away from reification of generics" read "moving Java towards the reification of generics"?