Dynamically Typed Code Is Just Not Good Engineering

I'm sorry but it's just not. Languages like JavaScript, Python, Ruby, Lisps, and (my personal favourite) AWK are fine for writing simple bits of automation and general operations stuff but code that is going to be executed in a production environment, code that has to be reliable and maintained for years to come, simply has to be written in a language that has native support for strict, static typing.

There is nothing that improves my productivity more than having the supercomputer in front of me analyse my code for stupid mistakes. Dynamically typed code is like word processing without a spell checker. I would honestly rather be forced to use ed to edit all my code but keep my type checker than go without. When writing code I make stupid mistakes all the time, and I really don't think that I'm bad at writing code. The only thing I can conclude is that most devs that swear by dynamically typed languages are blind to their own mistakes.

I also don't buy the argument that testing can do the same job. Even a test suite that asserts the correct behaviour of every line of code can't be as thorough as even the most basic type checker can be. My recent experience convinces me of this even more than ever.

Yesterday, I was doing a bit of an audit of gradually typed codebase -- the language and project are immaterial -- to look for places where the typing code be improved. I noticed a hole I'd punched in the type system a few weeks ago by instructing it to not type check a property test and I'd clearly forgotten to go back and add proper typing. Anyway, I add the proper types and then spend the next hour trying to convince the type checker that the code was correct, after all the tests were and had been passing and they had to be correct, right?

Wrong. The type checker had found a bug in the tests. Testing wouldn't have caught that; what are you going to do, right unit tests for your unit tests?? I had a test that whilst passing wasn't asserting what it should be, and sure, I should have noticed that, but coding is hard and why should I have to do something that the machine can do for me? Basically this experience has been the straw which broke the camel's back. I'm now firmly of the view that static type checking is non-negotiable; to do otherwise is reckless and unprofessional.

Moreover, using gradually typed systems like TypeScript, Flow.js, and presumably Python's new type annotations isn't good enough either. Not all libraries come with type definitions, especially when there are competing type systems for a given ecosystem. As such, you either need a collective effort to write type definitions and hope that all your dependencies have been included. Or you need to write your own, which is a waste of effort if everyone using a library is writing their own, which are never going to be as correct as the original author would have written them. And the type systems that are bolted onto these languages typically can't adequately capture the behaviour of the underlying dynamic platform, like functions that take strings, numbers, and other functions, with different outputs accordingly. Lastly, if nothing else suffices, you're left with a gaping hole in the safety that the type system can provide. If any of your dependencies lacks type definitions then even when the type checker says that there are no errors you cannot be sure that there aren't any. Just ridiculous.

Clearly, the only correct option is to use something that is adequately typed. In the procedural world that would be something like Rust, Zig, or Go. In the functional world that wound be something like Haskell, PureScript, or Elm.

I just don't understand how this isn't a no-brainer, I really don't.

~~~

Last Updated: 2022-10-01

..