Servant or Disciplinarian
One of the missing dimensions in this discussion about static and dynamic typing is where a language fits on the continuous spectrum of servant at one end and disciplinarian at the other. People who complain about having to spend too much time arguing with the compiler are wanting more servant and less disciplinarian, and those who feel that more static type checking will be helpful are asking for more discipline.
Different situations need different positions on this spectrum. I think the arguments have been ignoring this, and the all-important missing statement in brackets that should precede every declaration is: "[in my situation] more (static|dynamic) properties are better."
The situations are usually more about people than they are about programming. And for some reason programmers don't like hearing this, but the second missing dimension in this discussion is that programmers are different. There's a huge difference between novice programmers and the mysterious "5% who are 20x more productive than the other 95%."
This is probably a threatening thing to say because it can be tied up with things like money and social acceptance and the perceived quality of our (as Woody Allen said) "second-favorite organ." But why should someone who can swing a hammer believe that they have the same experience or ability of a master finish carpenter?
I remember coming out of school with a Master's degree in Computer Engineering (my undergraduate degree was in applied physics). I actually did know some things, and I could figure other things out. But my cubicle-mate, Brian, could build software. He had spent enough time thinking about it and doing it that he had perspective on the problem. That certainly didn't make me useless -- I created valuable things while I worked at that company. But at the time, we were just beginning to use pre-ANSI C for hardware programming, and there was very little type checking, and I could have used more. Of course, pre-ANSI C was not much more than a glorified assembly language and there was hardly any type checking at compile time, and none at runtime. This might be part of the confusion about dynamic languages, that it seems like you might be thrown back into working without a net, as we were doing then.
On numerous occasions I've heard Smalltalk programmers say that if a programmer starts with Smalltalk, it fundamentally shapes how they approach all programming (they also talk about unlearning the damage done by other languages in order to learn Smalltalk). I think this is probably true; most of the people I know who started with Smalltalk have a much better grasp of the fundamental concepts, and an ability to see what is simply the cruft of a particular language, than people who start with languages that are closer to the metal.
I started with assembly, then Pascal & C, then C++, then Java, then Python (ignoring a number of flirtations with scattered other languages like Prolog, Lisp, Forth, and Perl, none of which really took). So my experience with languages started with the cruft and the important concepts mixed together. Not only could I not tell one from the other (so they all seemed equally important) but the higher-level concepts were not initially available.
In C++, I started by creating my own containers because there was no STL for many years (the STL was actually added rather late in the standards process, and its initial goal was as a set of generic algorithms for functional-style programming, and not as a set of containers which is where it gets most of its use). All I wanted, initially, was a holder that would expand itself -- to a C programmer, stuck with fixed-size arrays, this seems rather revolutionary. And so it didn't seem strange that Java would have a separate library for containers. But Python doesn't even bother with fixed-size arrays. You just use a list, which is always there without importing any library, as common as air, as are dictionaries (Maps to C++ and Java programmers). Now, even sets are first-class objects. In C++ and Java, collections are intermediate level concepts, and so some programmers don't use them. It's quite difficult to know that some libraries are more important than others; in my initial experience with C, my tendency was just to write all my own code and not rely on library functions, so it was quite awhile before I understood that malloc() and free() had special importance, and the whole possibility of dynamic memory allocation (I really did come up the ladder from hardware). I now know intimately how lists and hashtables work, but I also wonder how different I might have been had I started with a language where dynamically-sized lists and dictionaries were just part of the fundamental toolset. My experience now, and what I see with those who started with Smalltalk, is that you take those concepts with you into languages like C++ and Java, and you are able to manipulate those languages more effectively as a result.
My point is that programmers are all on different positions on their own learning curve, and there's some intersection of that curve and the level of discipline that they and their team need. This is an issue that is based heavily in the human dynamic of the team. That is, it depends on who you have on your team and how they work together. Some teams benefit from the extra structure provided by a static language, others need the rapid abilities of a dynamic language.
However, much of the argument around static vs. dynamic languages seems to be about finding bugs. I will make two observations about this. First, both static and dynamic languages still produce bugs. And no matter how many static type checks a language includes, bugs still happen. I think there is a belief in some camps that we simply don't have enough static type checks in Java, and if we had more then eventually the compiler could guarantee provably-correct programs. And this leads to the second point: Java has a lot of dynamic checks. And it benefits from a lot of dynamic abilities, such as reflection. In fact, I think it could be safely argued that Java straddles the static and dynamic language worlds. For that matter, Python does a little bit of type checking during its compilation phase (and there is talk of adding more).
In the end, languages need, I think, to be opportunistic about when they can discover errors. And the best time to discover these errors is not always clear. Sometimes it's quickly obvious: it seems unlikely that array-bounds-checking at compile time is feasible, for example, so Java does it dynamically, at runtime (and C++ doesn't do it at all). Many of the arguments seem to be about when type correctness is established. The static approach is to force explicit declarations, the dynamic approach is to perform checks at runtime. In between is type inference, where you don't have to explicitly declare types but they're checked at compile-time anyway. An even more interesting approach would be a combination of type inference and dynamic checking, which produces the best of both worlds.
Perhaps what I most value about the dynamic language experience is that I feel it has opened my mind a bit more, indeed, as learning any language has. Even if much of your day-to-day programming is with a language like C++ or Java, I believe that learning a dynamic language will not only add another very valuable tool to your set -- and one which may allow you to solve "side problems" much more quickly -- but it allows you to go back to your "main" language with a new perspective, and one that will help you solve problems in a more effective and elegant fashion.
MindView Home Page