Static Typing Slows Down Development
It’s true. If you have to think about the types of your values when you are writing your program, it will take longer to write, especially in languages where type inferencing is weak or non-existent, forcing you to constantly redeclare the types of values the compiler should already know about. Typing those characters takes time, even if you’re using keyboard shortcuts and code generation tools!
So what’s so special about type inferencing anyway? Let’s take a look at some Haskell to illustrate:
respond :: String -> String respond s = "(╯°□°）╯︵ " ++ reverse s
Here, the first line is a type signature indicating that
respond is a function from
String (the input) to
String (the output.) The function body reverses the input and appends (
++) it to somebody with anger management issues. When run, it works as expected:
> putStrLn (respond "sounds like somebody has a case of the mondays") (╯°□°）╯︵ syadnom eht fo esac a sah ydobemos ekil sdnuos
In Haskell, type signatures are generally optional because the compiler can infer the relevant types without help from the author. That means this version of
respond is the same as the above:
respond s = "(╯°□°）╯︵ " ++ reverse s
In Python, this can be written as:
def respond(s): return "(╯°□°）╯︵ " + s[::-1]
which works the same:
>>> print(respond("sounds like somebody has a case of the mondays")) (╯°□°）╯︵ syadnom eht fo esac a sah ydobemos ekil sdnuos
So Haskell and Python are the same, right? Yes.
Wait. No. There’s a difference.
In Python, if we start speaking in numbers, weird things happen:
>>> respond(6174) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in respond TypeError: 'int' object has no attribute '__getitem__'
When we call the
respond function with a number, we get an error. Python has realized that it’s trying to treat a number like a list in order to reverse it, and shuts everything down until it can figure out what the hell is going on.
“Hey! You just said Python and Haskell aren’t the same, but Python has inferred that it’s an int! Also your memes are making me very upset.”
Python didn’t so much infer the type as trip over itself. It’s true that Python figured out that it was trying do something invalid on an int, but it did so at the very last moment, at which point it can only throw an exception in response.
BEHOLD THE ARCANE MAGIC OF HASKELL, SAVIOR OF MANKIND
-- respond.hs respond s = "(╯°□°）╯︵ " ++ reverse s main = putStrLn (respond (6174 :: Int))
Let’s compile it:
% ghc -o respond respond.hs [1 of 1] Compiling Main ( flip.hs, flip.o ) flip.hs:5:27: error: • Couldn't match type ‘Int’ with ‘[Char]’ Expected type: String Actual type: Int • In the first argument of ‘respond’, namely ‘(6174 :: Int)’ In the first argument of ‘putStrLn’, namely ‘(respond (6174 :: Int))’ In the expression: putStrLn (respond (6174 :: Int))
“This is not useful at all, you are just demonstrating the basics of static vs. dynamic typing. In statically typed languages, type errors are caught at compile time, and in dynamic languages at runtime. Everyone knows this.” — Michael Scott
Ehrm. Excuse me.
It’s true that the Haskell compiler simply knows the program’s types at compile-time and can output an error, but notice that we never said we expected a string in this program. That was all inferred from the first argument to
(We did state that
6174 was an
Int. Nobody knows why we had to do this. Archeologists believe the answer was lost with a clan of highly intelligent synapsids that went extinct at the end of the Triassic period.)
Because Haskell has a powerful inferencing system, we can write whole programs, without any type annotations, that are statically checked at compile time:
-- program.hs respond s = "(╯°□°）╯︵ " ++ reverse s addTwo n = n + 2 snipSnip xs = tail xs main = do putStrLn (respond "salmonatus quichen!") print (addTwo 8) print (snipSnip "<o>") -- you're gonna lose that arm!
% ghc -o program program.hs % ./program (╯°□°）╯︵ !nehciuq sutanomlas 10 "o>"
Mom, where are the types?
This program will compile and run just fine, but supply
addTwo a string or
respond a number and you’ll be in big trouble. Big.
Now imagine you have over 9,000 functions that are all statically verified like this, and you put them into a tube. That means you’d end up with a very long tube. Also, no part of the tube would be broken—now or after making modifications.
Anyway, yeah, so the point or whatever is that while dynamic languages require very little thinking about types, they make no effort to try to validate that your program is actually correct. It is faster to develop applications with them because you don’t constantly have to declare (or even know) what types you are working with. However, much frustration with static types comes from the verbosity of the implementation of various programming languages, not from static typing itself. It is possible to get the benefits of static typing (programs compile until they are no longer correct, rather than run until they try to do something horribly wrong) without having to actively think about or annotate types.
(In fact, many users of statically typed languages like Haskell choose to type out type signatures, but as a sanity check on themselves, not the compiler, and as documentation. They don’t have to.)
When the negative mental overhead usually associated with static types is avoided, they can greatly reduce the number of error conditions your program can encounter, reduce the number of tests you have to write (because even though there can be logic errors, there are simply a large number of things your compiled program can’t do incorrectly, so why test for them?), and even make you enjoy thinking about them (creep.)
Interestingly, learning Haskell gives you ideas that can be applied to other programming languages in order to address issues like lack of typechecking. Even if you don’t use Haskell, learning it helps you write programs that are safer, more maintainable, and faster, not slower, to develop.