Static versus dynamic typing

Dirk van Deun, dirk at dinf.vub.ac.be
March 2010

I am really tired of being pushed into a camp in the religious wars between static and dynamic typers. I have taken the time to carefully lay out my ideas on this question, so that from now on I can just refer to this URL and feel at liberty to stay clear of shouting matches.

It is pretty useless to debate the merits of dynamic and static typing in isolation. They both come with a culture. Dynamic typing goes very well with interpreters and keeping all stuff first class. Static typing combines best with compilers. Programs of the first culture can often access knowledge about themselves at runtime and even change it, thereby changing their own behaviour, an ability that is aptly named reflection. To keep up the analogy, we could state that programs in the second culture behave mostly instinctively: they only contain behaviour, and the knowledge has been "compiled away".

Static typing and compilation exchange reflection for safety and efficiency. The static type checker helps the compiler improve efficiency by optimizing the lay-out of data in memory, in particular with fewer indirections and tags; by determining which dynamic checks need never be made, and which information needs never be consulted at run time, because they can be predicted; and also by enforcing rules that ensure a high degree of predictability. Furthermore it only allows code that should never lead to dynamic type errors at run time, so-called type safe code. Thus the only dynamic checks that may remain are those for dynamic dispatch and typed pattern matching [1].

Type unsafety is a good predictor of bad code, particularly of errors in the plumbing of the program, the parts that just concern passing data around. But there are many false positives, programs deemed unsafe that are not otherwise incorrect, which means that a programmer must program to the type system, which can be a hassle. A small change may involve a lot of work. Dynamic typing on the other hand means less hassle up front, and more flexibility to keep the impact of changes localized, but programs have to be tested more thoroughly. Refactoring in particular, which is not so much about changing algorithms as about changing the plumbing of a program, may introduce errors that have to be shaken out by testing, whereas refactored code that is found safe by a picky static type checker, like that of Haskell, will have a very high probability of being correct, or at least as correct as it was before the refactoring.

It should be noted that a lack of more appropriate language constructs in mainstream imperative and object oriented languages may lead the programmer to using type casts, the safety of which is assumed by the type checker but left up to the programmer to ensure. Also, many languages and their type systems cannot conveniently express "no value", so that for instance zero values are routinely used as "pointer to no object", an implicit type cast that is the cause of many run-time errors. Such old habits from low-level languages like C in higher-level languages significantly undermine the power of type safety to signal common bugs. (Of course in C, static typing was intended to be porous; it was mostly for efficiency.) Scala is a newer object oriented language which gets many such things right, with for instance typed patterns and Option types.

Dynamic typing can deal with reflection, whereas static typing, although it can be made to work with specific reflective features, in general cannot. This is because the ability of a program to change itself makes it thoroughly unpredictable, hence hard to analyze by a static checker. So sometimes dynamic typing is the only sensible choice, as for instance for Ruby. The flagship application of that language, Rails, derives much of its convenience from the ActiveRecord component that can examine the structure of database tables and use reflection to create Ruby classes on the fly to mirror them; a Ruby with static typing might not have found its niche.

At the other end of the spectrum, pure functional languages reason in terms of abstract values and mathematical truths only, not in terms of objects and assignments and procedures stored in memory, so reflection makes little sense for them. They don't lose much by using static typing. They gain a lot, however. In the absence of side effects, and in a culture that encourages juggling with partially applied functions, the plumbing of a program can become very complex, and this is just the kind of complexity that static checkers can check. Monad transformers for instance consist of almost nothing but mind-bogglingly complex plumbing. Haskell programmers are more than happy to accept the hassle in return for the debugging help. (Haskell programmers are the world champions in turning lemons into lemonade. First they turned the absence of assignments into a good thing; then they enriched hasslesome, boring old static types, so that they became an expressive language for machine-checked partial specifications.)

In conclusion, the choice between static typing and dynamic typing should match other choices, such as the choice of a programming paradigm. Sometimes dynamic typing is obviously best, and sometimes static typing is. But in the huge area in between, the choice is a trade-off between safety and efficiency on the one hand, and reflection and flexibility on the other, based on how much can be gained in each of these four domains in a particular language or for a particular programming task.

I have spoken.

[1]   I might forget, or not be aware of other cases, but if these exist they should be rather obscure; and let's face it, this is not the only sweeping statement in this essay. Precision about so large a topic would probably require a thesis instead of an essay, which I do not want to write, and you probably do not want to read.

Back to Dirk van Deun's Web 1.0 blog