I would like to talk about static typing and dynamic typing. This topic has been written on and debated a lot on the Internet and I’m no Simon Peyton-Jones, Larry Wall or Alan Kay, so these opinions are probably nothing new. I am not bringing anything new to the debate, except perhaps bad metaphors. The reason I am writing this post is that I can’t seem to figure out what I think about typing, and I want to write it down to hopefully untangle my own thoughts.
I’m also more interested in the reliability vs flexibility aspect of the debate, so I will not be discussing the issue of speed.
Definitions
To make sure we all understand each other, I’ll start by defining some terms.
Static typing: A typing system where the values and the variables have types. A number variable cannot hold anything other than a number. Types are determined and enforced at compile-time, or declaration-time.
Dynamic typing: A typing system where the values have types, but not the variables. It is thus possible to successively put a number then a string inside the same variable. Types are determined and checked at run-time.
Weak typing: weak typing is often used to mean dynamic typing, however they are two different things. Weak typing refers to how typing is done. Examples of weakly typed languages include JavaScript, Perl and PHP (which, incidentally, are also all dynamically typed languages.) A weakly typed language will do type conversions in the background if the programmer hasn’t done them explicitly. For example, in Perl, 3 + "4" returns 7. The + operation is specifically defined to operate on numbers, but since "4" is a string, Perl implicitly converts it to a number before performing the addition.
Strong typing: strong typing is the opposite of weak typing. This family includes Lisp dialects, Python, Haskell, etc. A strong typing system will not try to implicitly convert values for us. With those languages, the previous example would fail. We would need to explicitly convert "4" to a number.
Type inference: A technique by which the compiler determines the type of a variable or function without help from the programmer. For example, the compiler can deduce that the variable s in s = "Hello" will have the type string, because "hello" is a string.
OK, with definitions out of the way, let’s see what’s on my mind…
Static typing is great…
We live in an era where the influence of computers has never been greater: we work from home, we buy products from Internet stores, we pay our bills online, the billing systems of every company is computerized, the global market is just a bunch of bits, etc. In a time where so much of our society’s infrastructure is dependent on computer software, it is essential that those programs be reliable (something that doesn’t happen often enough, unfortunately.)
One of the many things that can help us make sure that our programs are safe and sound is static typing. Or at least, that’s what the static typing advocates would have us believe. With such a system, all the interactions between the different functions and variables are checked during the compilation phase and any mistake is treated as a fatal error, and the compilation stops. With static typing, it is impossible to build a program that would try to apply the length function to a number. With a dynamically typed program, the compiler wouldn’t check for that possibility, and the error would happen at run-time.
That sure makes static typing sound like a sweet deal! After all, humans program computers, and we’re not known for our infallibility. In fact, we’re quite the opposite: anything can disrupt our concentration and this can lead to the introduction of bugs in our software.
Surely, it is nice to know that the compiler is there to point out our mistakes when we’re not fully focused. Static typing also helps us make modifications to existing code: if it doesn’t compile, we know there is a problem with our modification. It also helps us use the code of other programmers, because there is a protocol on how functions should be used.
In the Haskell and ML communities, there is a saying: “if it compiles, it works.” This is not a fact, of course, static typing cannot help us with everything that could go wrong in our program, but it shows how dependant these programmers are on the type checker to find common mistakes (and even not so common mistakes.)
… but so constraining!
This safety comes at a price however: some tasks become a lot harder with a statically typed language and a few are practically impossible. For instance, in Haskell the functions fst and snd respectively return the first and second element of a two-elements tuple. They do not work on tuples of any other size. It is not possible to write a generic version of those functions.
Static typing also impairs code generated at run-time. A lot of dynamically typed languages have a function called eval that can evaluate arbitrary code at run-time. Although the use of eval is generally discouraged, it can be used to generate code that simplifies the programmer’s task. One of the best known example is Ruby on Rails, which creates methods with the names of the columns of a table in a database for a model. This sort of code generation is much harder in statically typed languages, because the code is generated after the compilation phase, and therefore, it cannot be type checked without being ran.
Dynamic languages such as Smalltalk, Lisp and Erlang also allow hot code swapping: we can load new code while the application is running and the new code will immediately be used. I have yet to see this in a statically typed language.
Another problem of static typing is that it introduces an extra layer of complexity in the language; the static languages all have their own set of extra features (templates, generics, type classes, etc.) to make sure that the compiler is satisfied that what we are writing is sound.
def my_len(L):
x = 0
for e in L:
x += 1
return x
The previous Python function (naïvely) returns the length of a list. The list can contain elements of any types. (Actually, this isn’t true; it doesn’t work only on lists, it returns the length of any iterable object.) To write the same function in C++, we would need to use templates to convince the compiler that the function is type safe.
Dynamic typing is like flying!
At the other end of the spectrum, we have dynamic typing. Dynamic typing is also perfect for the kind of world we live in: face-paced and ever changing. Clients want their software really quickly, they want their software to be extremely flexible and they will change their minds about what it is they want three times in the first month. And programmers are expected to deliver, we do not have the luxury of bridge engineers to say “it cannot be done.” Dynamic languages are extremely flexible in what they allow the programmers to accomplish.
Generally speaking, dynamically typed languages are much more permissive than statically typed languages; in a sense, they seem closer to how “real life” works. If I’m packing a bag for a trip, I can put anything I please into it. The bag will not spit out my iPod because there are clothes in it and it can only hold one type of objects. (I told you the metaphors would be bad.) This is how lists (or arrays) work in dynamically typed languages: we can put anything we want into them, the language doesn’t mind one bit.
This flexibility is really helpful when we want to quickly develop a prototype, when we want to test something, or when we’re not even sure of how something is going to work. For example, instead of using a proper tree data structure and creating all the functions to support that tree, we could simply use a regular list — which can contain scalar values (leaves) and other lists (branches) — to explore if a tree seems like an appropriate structure for the problem at hand.
As I mentioned earlier, dynamically typed languages also offer features that are much harder to implement in statically typed languages.
Hot code swapping is an extremely powerful feature that I’ve seen only in dynamic languages. Being able to load new code without shutting down an application is a really important feature to have if we want programs that can run for months, even years without interruption. The most common example are the Ericsson switches written in Erlang which have nine nines of reliability (99.9999999%), but the benefits are also visible in applications like Emacs where a user using elisp can add significant functionality to the program without having to restart it.
I also briefly mentioned that dynamically typed languages are the kings of code generated at run-time. This feature has been used to fix existing methods, add methods to existing classes, generate classes and methods at runtime, using the programming language as the configuration language, etc. We can use this to “fix” parts of the language, improve parts of the language and grow the language.
Dynamically typed languages are also often simpler than static languages. The rules of Forth or Lisp can be learnt in a couple of hours; I know a lot of programmers who learned Python in an afternoon. I don’t believe anyone can claim that the rules of C++ can be explained this quickly (much less mastered.)
But without a parachute!
This flexibility and simplicity is achieved by assuming that the programmer knows what he’s doing. But we agreed that humans are fallible, so how do the programmers working with Python and JavaScript manage to create solid software? With a whole lot of testing. By writing automated unit tests, the programmers make sure that all the functions of their program behave as they expect them with valid and invalid input. Any time they change something in the program, they re-run the tests to make sure that they didn’t break anything.
Dynamic typing advocates claim that type errors are actually pretty rare and that unit testing makes static typing unnecessary. While it is true that tests go a long way toward making software reliable, they are not a proof, they are a demonstration that the code behaves properly for the subset of inputs that they test. Also, because tests are written by the same fallible humans, they could possibly be wrong.
The myth of short code
A common misconception among some programmers is that the reason why people like dynamic typing is because it makes the code shorter. We don’t need to type those pesky type descriptions everywhere, so the resulting code is obviously shorter.
The existence of type inference nullifies this point. With good type inference, a programmer can enjoy the benefits of static typing with the terseness of dynamic typing.
This does not mean that type inference closes the debate and that we should all use static typing. As I described earlier, they are other more fundamental differences between the two typing systems. Furthermore, I believe that powerful abstraction mechanisms are the more likely culprits for short code. Ruby (dynamic) and Haskell (static) both usually require less code to write the same things as in Java (static) or PHP (dynamic). I firmly believe that the reason is that Ruby and Haskell support more advanced abstractions (like lexical closures ;)).
Optional typing
In a typical “we want our cake and it eat too” fashion, programmers have asked about static typing and dynamic typing “can’t we have both?” Some languages already support the mixing of both types. In Common Lisp, a dynamic language, it is possible to declare the type of variables. The next versions of Perl and JavaScript, both dynamically typed, will have optional static typing. It was announced recently that C# 4.0 would have a dynamic keyword to allow optional dynamic typing.
The obvious question then is: “how should optional typing be done?” Should we add static typing to dynamic languages or vice versa? I thought about this, and I think that the former is preferable; create a language that is flexible and can be used to explore a problem space and its solutions, and when the prototyping is over, make it easy to add type annotations to get the safety of static typing.
Conclusion
I stated in the beginning of this post that I wanted to try and untangle the different opinions I had on type systems, and I believe this post was successful in that respect.
I had a very easy time talking about the importance of reliable software, I cannot imagine not caring that a program I build is robust. I also had an easy time talking about the importance of flexibility in the current market.
This leads me to believe that both approaches are valid and can be used to make solid software, but that languages which will be able to mix both techniques and give to the developers the advantages of both systems are obviously going to become very popular in the near future, and I can already feel that I’m going to like them.
Finally, I would like to thank Jeremy Fincher and Steven Pigeon for proof reading this post.