The source for this post is online at 2013-08-12-types.scrbl.
An ancient debate among programmers and programming language researchers, like myself, is whether it is better to have a static type system or not. In the age old tradition of Protagoras and Cratylus, I attempt to thwart this technical argument by an appeal to morality: technically superior or not, static typing is inhumane!
Although many of you, no doubt, have a clear vision of the enemy, that common whore of mankind, I shall first describe my target: static typing.
A static type system is a mechanism whereby an algorithm determines if a program exhibits a property, P, and if the property is not found to hold, then the program is rejected. A language will often employ a static type system in such a way that such rejected programs are not (or cannot be) compiled or run.
For example, my second favourite language, Coq, has a type system that includes as one of its properties strong normalization, or guaranteed termination. (Some of the plebs out there may think it is impossible to "disprove" the Halting Program Argument and guarantee termination, but recall that this argument relies on a sufficiently powerful source language, which Coq is not.)
Other more quaint languages like Java may have type systems that check properties such as "The program does not add numbers and strings" or "The program does not jump to non-program code location." (Of course, most type systems really check exactly one property that entails these, and other, properties.)
These many statically typed languages have in common the prohibition against programs that do not "pass" the type checker. Indeed, this rejection is the entire purpose of the type systems. The logic goes that if you reject "bad" programs then only "good" programs remain, and who would want to run "bad" programs anyways?
Unfortunately the great flaw in this argument is the presumption that the programs rejected by the type system are only the "bad" programs when, in fact, nearly every type system rejects infinitely many "good" programs as well. For example, it is a rare type system that would accept this program:
Why would a type system reject this? Most type systems do not attempt to reason about the different run-time paths a program can take, so they "merge" their conclusions on the two sides of an if expression. The consequence of this is that all sides must return the same thing: a number or a string, but not both. This program appears erroneous to a type system for this reason: the outer context of the if mandates a number, but the if "could" return a string, so an error is "possible", so the program is rejected.
Of course, this example is so delicious because the type system is wrong. If we assume that fahrenheit->kelvin lives up to its name, and if you don’t want to assume that, just replace it with (λ (f) (* (+ f 459.67) 5/9)), then it is impossible for the if’s test to be anything except false. Thus, the if will always return 2 and never exhibit the error.
There are some type systems that would accept this program, however. For instance, the Sage programming language by a co-author of mine, Cormac Flanagan, dispatches formula such as "Is that expression ever negative?" to a totally automated theorem prover that might easily check this particular expression and therefore allow it through. (By the way, I don’t know if Sage actually does accept this, but something like it certainly could.)
However, we can imagine more and more absurd if statements that we would have no doubt that a type system could not analyze. In the final analysis, we may appeal to the Gödel Incompleteness Theorems and conclude that in every logic there are statements which are true, but not provable, or provable, but not true.
And this is the ultimate problem with type systems: in their quest to reject "bad" programs, they must reject "good" programs as well because they cannot prove their "goodness".
A corollary, which I won’t go into detail on, is that the "goodness" a type system purports to verify is only related to its specific properties and not the human ends of the software. For instance, even Haskell will not check if your physics simulation is actually the correct one, it will only check if you correctly applied your monad transformer correctly on the right multi-parameter type class instance. (This, by the way, is why I prefer Coq, because it allows actual correctness verification is a static way.)
Until now, I have merely been preaching the technical consequence of how type systems and logic work. But now, I shall go for the jugular.
Advocates for static typing are anti-human, because they argue that the only programs we should allow to run are the ones that have been verified by machines! In contrast, the freedom fighters contra to these typing terrorists argue that humans can perform analysis and decision making as well!
The villains of static typing hegemony point to the line where their algorithms fail and say, "This is the line and we shall go no further!", but the heroes of the dynamic few look upon the wide horizon and say, "I shall go and seek a new land flowing with milk and honey."
My own belief is that verification is good and we should use it whenever we can, but we should not let human accomplishment be held back by a fear of the unverifiable. Once we know that the way is clear, we can try to encode our human wisdom and verify more. We should blaze the frontier and return and build roads so that feebler minds can trust in the highways we create. I am reminded by the classic Mormon proverb: "Trust in God, but brush your teeth."
I believe that this humanistic ideal is exemplified in Racket, the world’s only full spectrum programming language, where the human mind and static analysis are wed together to complement each where the other fails. We have not yet achieved the dream of a Verified Racket where you can conduct Coq-style program verification, but we may yet be on Mt. Pisgah and can see it afar off.
But first let’s remember what we did today!
The sinister bands of mongrels known as static typing advocates trust in the weak arm of silicon that leads them astray with false promises of freeing them from "bad" programs and delivering them into "good" programs.
This is a necessary situation due to the incompleteness of logic in the face of powerful programming languages.
Racket attempts to skirt this situation by respecting humans and providing analysis tools, for those humans to use.