Static Typing may not Scale
I recently got to thinking about the future of programming. I believe that as systems grow ever more complex and networked, we’re headed for a future where programming won’t be about editing code in text files, compiling them, and restarting programs (wash-rinse-repeat), but rather, incrementally building live systems, and performing microsurgery on running programs, without interrupting them. As we work on ever bigger systems, we’ll need all the debugging help we can get, and type systems sure would come in handy, but there’s just one issue with that: I think that static type systems, and type inference as implemented in languages like Haskell won’t work so well with such a model. I think the truth is that dynamic languages are much better suited to the incremental, organic model of software development we’re getting pushed into. Dynamic languages are here to stay. They’ve been gaining in popularity, and this trend isn’t going to reverse, it’s only going to grow stronger.
Imagine you’re working on a live system. You’re modifying the code for a program that’s currently running on some remote server. Now imagine this system is statically typed. You want to make a change to a method somewhere, but unfortunately, this small change would cause a type error. You need to modify several other functions elsewhere in the system, as well as schema/record declarations. The statically typed approach would probably involve making all of your code changes, changing the schema declarations, running some kind of type inference algorithm, verifying that everything checked, and then running some sort of “commit” that recompiles the needed code on the remote machine and translates your records to the new format, probably interrupting the program while this is happening (and who knows how long this might take, could be hours). The big issue here is that with current static type systems, you can only go from one statically valid program to another statically valid program, which seems poorly suited to the incremental modification of a running system.
The dynamically typed approach approach might be to write code to handle the new schema before it’s even introduced, then run some code to translate from the old schema format to the new (all on-the-fly), and watch the system adapt, translating your records without a pause. Once all records are translated, you can then remove the code that deals with the old schema. I think the great advantage here is that a dynamic language allows you more wiggle room to make things temporarily inconsistent and refactor the system in an incremental manner. With dynamic languages, you can have two record formats for the same data at the same time, and code that has typing errors won’t cause the system to fail so long as it’s not executed. It’s a more organic approach to the problem, and I think that programming live systems might actually be inherently organic in nature.
Now, I stated early in this post that I believe type systems and type inference are actually quite helpful. You could actually make the argument that organically modifying massive online systems on-the-fly without type checking is about as safe as riding a dirt bike through a Serbian minefield or playing Russian roulette everyday for a year. My answer would be that type inference isn’t fundamentally incompatible with dynamic typing. The unification-based type inference algorithms that Haskell and ML use don’t apply to dynamic languages, because they’re trying to solve a system of constraints where every variable has one single type, but unification isn’t the only type inference algorithm in existence. It’s quite possible to create a dynamic language VM that does type inference, and warns you about possible type errors. The difference is that the dynamic language VM will let you have temporary type errors, so long as you know you can make things eventually consistent.
What if you were modifying a live system, and introduced some crash bug? If you were working on a large production system, this could have terrible consequences. I would argue that if we’re going to modify live systems, we probably need to introduce live debugging tools. Things such as the ability to snapshot the system before introducing code changes, recording traces and being able to backtrack execution some large number of steps should a crash occur. It may also be interesting to be able to install monitoring on new pieces of code to keep track of what these specific pieces of code are doing after their introduction. Ultimately, however, with live systems, we should be prepared to have programmers directly intervening at the moment of a crash to repair the system on-the-fly and resume its execution. With the ability to backtrack execution and examine the live heap, this might actually turn out to be a rather comfortable way of debugging programs.