Once upon a time, I was not the programmer I am today - that’s not to say I’m a good one now, but I am significantly better than I used to be. As such, I now feel quite comfortable and confident about certain areas. This blog post is not about one of these things.
This post is about types. You know, those things that variables have. Those fussy little annotations that only seem to cause compiler bugs and just generally get in the way. Chances are, you’ve definitely had those moments where it just feels like you’re wrestling with the compiler. Why can’t it just get out of the way and let you do this really simple thing that in all actuality isn’t that hard, and the language is stupid, and the framework is stupid, and the requirements are stupid, and above all types are stupid.
I used to think this way. I could make anything I wanted to - and types just seemed to get in the way all the time. So, in this post, I want to talk about why I was wrong, and why types are actually quite neat and try and dispel a few myths about programming with types.
Types add mess to the code
One complaint I used to have about types was that they’re ugly.
To have a further look, let’s see what this looks like in python (possibly my favorite language of all time):
a = 3 b = a + 1
Beautiful. Elegant. Neat. There is no extra information here, no nasty types.
So what do other languages look like? Let’s try C++:
int a = 3; int b = a + 1;
ints aren’t so pretty, are they? Well, you know, that’s not too bad.
But what about cases where you find yourself doing things like:
std::vector<std::string>* thing = new std::vector<std::string>();
Yes, this example is contrived. But yuck. The type annotation is repeated, meaning that you’re writing out far more information than feels natural. This complaint is valid I think - but not as an argument against types, more as an argument against C++.
In rust, a much nicer and more modern language, we can simply do this:
let a = 3; let b = a + 1;
Rust is just as strongly typed as C++ (arguably more so), yet has a lovely
feature called type inference. This feature comes from functional progamming,
in languages like haskell, where functions frequently have exotic types like
Foldable t => (b -> a -> b) -> b -> t a -> b. Joy. It would not be
particularly fun if the programmer had to manually add the type annotations for
every single variable and function, so the language does it for you.
Rust has adopted this feature, and does it very very well. I find that I hardly need to add manual type annotations, and when I do, it’s for a very specific reason, because I’m generally trying to do something a little strange. And in those cases, the type annotation is hugely useful, as it helps the reader of the code to see what is going on better, when it wasn’t explicitly clear.
So yes, types are sometimes ugly. But when using a sane language (very much not like C++), that is frequently not the case. If you’re complaining about types being ugly, you’re making unfair comparisons - you can’t judge a state of the art dynamically typed language like ruby or python against something like C++ which is, you know, getting on. It makes sense to judge modern languages against modern languages - and in this case, types are hardly noticable.
Types are useless
One argument about types that was the be-all and end-all for me was that types don’t even provide that much benefit. You spend all day trying to convince the compiler that your code is actually not poison, and what do you get?
You get an assurance that your code is not going to do stupid things. In a
typo, you can end up trying to do silly meaningless things like adding a number
to a string. Or getting
undefined errors everywhere. Or finding that
refers to about a hundred things, but never the thing you actually want. Or…
that’s enough for now.
Typed code means that doesn’t happen. It just doesn’t. You can still get segfaults for trying to do things like access past the end of an array, but you’re not going to find that the type of something just changes randomly. The rules are strict, and yes, that does seem limiting. But the rules are ultimately there to protect you.
My experience has always been, that when the type checker catches a problem, it’s either something ridiculously trivial that can be fixed instantly, and it’s nice to have that little nudge back onto the right track - or it’s a big problem that’s actually a problem of how you’re trying to solve things. In that case, it’s very nice to have that pointed out to you by the compiler instead of at runtime with a nasty error.
But tests do all that!
So. When you get down to it. What’s actually the point of types? Beyond some obscure academic curiosity and making you feel annoyed at 2 AM at a hackathon (definitely not sour about that).
Types are ultimately there to provide guarantees about your code. That it works, and doesn’t do something stupid. But, surely unit tests cover those cases? Then why would you need types?
Well. First off, if you’re anything like me, while making test cases pass is
quite fun, writing the things is much less enjoyable and makes you want to cry.
One reason for this is that it’s really annoying to have to cover all the
cases in a dynamically typed language. What happens if you’re passed
What if it’s a list instead of a string? That’s never a problem when you have
static types. Using types reduces the number of cases you have to write tests
for, and you actually end up testing the logic - rather than the language.
Secondly, sometimes you can’t get 100% coverage on a test. Sometimes it’s not possible - you need to be hooked up to a network or a system that can’t be mocked, or you need to handle errors that only happen once in a blue moon. With dynamic typing, you have very little assurance about how well that code will actually run. With static typing, at least you can be sure that all of the values you’re dealing are reasonable, and that you’re handling all the cases appropriately. In languages like rust, you can even guarantee things like lifetimes and ownership using types.
I’m definitely not trying to say that statically typed code doesn’t need to have unit tests - that would be crazy. Unit tests are important. But, I think that with good use of static typing, you can eliminate the need for extraneous unit tests, and even provide guarantees about the code that you can’t test at all.
My past self would probably be appalled that I would write a post like this. As you can probably guess, I had strong opinions against them. But over time, I’ve discovered that types really can be the difference between writing good code and writing bad code. Now, if I had to pick a language and choose between static or dynamic typing, I would pick static typing almost every time.