3-31-04 I'm Over It
Some very interesting analyses have come out of my discussions of latent
typing. For example:
Pixel1
Pixel2
Pixel3
by an anonymous blogger named "Pixel" (no way to tell who it is from the
site). Although there are places where Pixel misunderstood what I was saying (or
more likely I didn't say it clearly enough), in general this states the "Java
case" reasonably well.
That case includes the regular argument that people are just as productive in
Java as they could be in Python, an argument which is almost universally made in
the abstract, without any direct experience in both languages. I have regular
experience in both languages, and the result is always: if you want to
get a lot done, use Python. The testing doesn't come, as Pixel argues,
with special hand-testing, but by using the actual data that you're trying to
manipulate. You just get there faster, and start finding the real problems,
using real data, faster. I have this experience over and over.
And the kinds of problems I solve are only theoretically solvable using Java.
It would take at least 5-10 times longer to do it, and that assumes you wouldn't
give up or get lost first. For example, you could translate something from
Python, but it would be much easier because you had the Python design as a
roadmap, and the resulting code would still be much bigger, messier, and (a big
point of argument, since people regularly claim that the more verbose Java is
easier to maintain because it's so much more explicit) harder to maintain.
In the follow up Pixel2, Pixel ends
by asking "How much value do you put on checking as much as possible at compile
time?" That oversimplifies the issue. If the type checking came at no cost, then
the answer would be as easy and obvious as Pixel implies with his question. I've
generally found that most folks who argue that strong static type checking must
be preserved and increased at all costs have no experience of a down side
because of it. And if you don't see any down side, then you think that arguing
against it is obviously crazy.
I know because I began firmly in the strong static camp, having seen the
benefits of moving from pre-ANSI C to C++. The C++ strong static type checking
found lots of errors that (especially pre-ANSI) C didn't, so it was a clear win.
And the extension of this philosophy to Java was thus obvious. But that was my
only dimension of experience for many years. It was only when I began using a
more dynamic, more succinct language (i.e. Python) that I got a new dimension.
The experience most people have goes something like this: they have had enough
exposure to Python to have it in the back of their mind. A problem arises that
might require a one shot solution. Knowing how much effort Java is, they think
that this is a good place to try Python, since it might save some effort. Then
they have the watershed experience of solving the problem in far less time than
it would take in Java. The next time a similar problem arises, they reach for
Python more quickly, until they start using Python as a preferred tool, only
using Java when there's no other choice.
During this process, the experience of being dramatically more productive in
Python repeats itself over and over. It's not a philosophical argument (because
the answer is "obviously" that the strong static language is going to be
better), it's direct experience. The correctness and quality of the resulting
programs is consistently very high.
Then you reach the intellectual vs. experiential crisis: you "know" that Java
is "better," but your experience is that Python is "better." How can this be,
when all the arguments that you "know" to be true clearly show that a strong
static language must be "better." At that point you start trying to resolve this
crisis by seriously questioning your preconceptions, as I have been doing. But
the only arguments you hear back are the ones that you yourself (I myself)
previously knew were true, and have been shown from experience to be uncertain.
What I'm trying to get to is that in my experience there's a balance between
the value of strong static typing and the resulting impact that it makes on your
productivity. The argument that "strong static is obviously better" is generally
made by folks who haven't had the experience of being dramatically more
productive in an alternative language. When you have this experience, you see
that the overhead of strong static typing isn't always beneficial, because
sometimes it slows you down enough that it ends up having a big impact on
productivity.
I can't quantify this. I haven't been able to come up with a from-first-
principles mathematical proof, probably because it depends on human factors,
like how much time it takes to remember how to open a file and put the try block
in the right places and remember how to read lines and then remember what you
were really trying to accomplish by reading that file. In Python, I can process
each line in a file by saying:
for line in file("FileName.txt"):
# Process line
I didn't have to look that up, or to even think about it, because it's so
natural. I always have to look up the way to open files and read lines in
Java. I suppose you could argue that Java wasn't intended to do text processing
and I'd agree with you, but unfortunately it seems like Java is mostly used on
servers where a very common task is to process text.
There have been studies done on how much time it takes to recover from
interruptions, but I can't make any direct connection other than to say that it
seems to me that this must have a big influence, both when writing the code
(yes, I know that much of it is automated with Eclipse and similar editors) and
reading the code.
But the title of this article ("I'm over it") means this: Java is what it is.
My discovery and reporting of the fact that Java generics doesn't support latent
typing (which turns out to be the same thing that the Ruby folks invented "duck
typing" to mean, not like prototypes where you add methods to objects, but just
in the sense of "if it walks like a duck, don't worry about asking whether it's
actually a duck." So Duck Typing is just the Ruby way to say "Latent Typing," a
term that really does have history in computer science) was primarily meant to
get people to show me how Java generics really do support latent typing in the
event that I had misunderstood something. They didn't, and I didn't, so that's
the case.
In the end, I've realized that the requirement that you have to use
interfaces everywhere really isn't that big of a deal. In Python, which is a
succinct language, it would really stick out, but Java is unashamedly verbose,
and many people seem to like the verbosity and feel that it is a benefit.
Requiring a few extra interfaces here and there is not much of an impact on the
resulting code, and is indeed in keeping with the Java language philosophy (it
would probably really surprise people if latent typing did have direct
support in the language).
So I understand that Java generics are really just autocasting for nicer use
of containers, and that's good and useful and it doesn't go any further than
that. The generics chapter of Thinking in Java, 4e will thus be easier to write
(nothing really complex will happen as in Thinking in C++, volume 2).
Finally, it is actually possible to do latent typing in Java, but you
really have to want to do it because it requires more effort. Let's revisit the
dogs and robots program one more time, this time using reflection to produce a
method that uses latent typing:
import java.lang.reflect.*;
class Dog {
public void talk() { System.out.println("Woof!"); }
public void reproduce() {}
}
class Robot {
public void talk() { System.out.println("Click!"); }
public void oilChange() {}
}
class Mime {
public void walkAgainstTheWind() {}
public String toString() { return "Mime"; }
}
class Communicate {
public static void speak(Object speaker) {
try {
Class spkr = speaker.getClass();
Method talk = spkr.getMethod("talk", null);
talk.invoke(speaker, new Object[]{});
} catch(NoSuchMethodException e) {
System.err.println(speaker + " cannot talk");
} catch(IllegalAccessException e) {
System.err.println(speaker + " IllegalAccessException");
} catch(InvocationTargetException e) {
System.err.println(speaker + " InvocationTargetException");
}
}
}
public class Latent {
public static void main(String[] args) {
Communicate.speak(new Dog());
Communicate.speak(new Robot());
Communicate.speak(new Mime());
}
}
I can thus call speak() on anything, and it will only invoke
talk() on objects that actually have a talk() method.
The output is:
Woof!
Click!
Mime cannot talk