On Ruby and type checkers
Stripe recently open sourced Sorbet, a ruby type checker. It was also recently announced that types will be coming to Ruby 3. Along with the impact of Typescript and with types coming to Python, it is evident that static type checkers for dynamic languages are gaining a lot of traction, and this piece is my attempt to clarify my own thoughts about it.
- On the one hand, I don’t miss them at all.
- On the other, a lot of smart people and companies are embracing them, and this makes me want to give them a try.
When you read about what benefits types bring, there are always 3 recurring themes:
- Better support for editors
- More robust systems
- Better documentation
Code editing support
People often speak about how wonderful is autocompletion, being able to rename things or find usages when using Typescript (and now with Sorbet). If you come from using “Find and replace” I totally see how this can blow your mind. I have been doing this with Rubymine for many years so it doesn’t sound that revolutionary to me.
It seems that type annotations will standardize IDE-like services for dynamic languages so that any plain editor can leverage on them to offer advanced code assist. I think this is a good thing, but not be a game changer if you are already using an IDE.
To me, this is the most intriguing aspect of the types discussion. I observe 2 recurring reasons for static type checkers:
- It decreases the number of bugs
- It improves collaboration by large teams on large codebases
TL;DR: both Flow and TypeScript are pretty good, and conservatively either of them can prevent about 15% of the bugs that end up in committed code.
15% fewer bugs with types is a pretty bold statement. I applaud the scientific attempt to analyze the impact of types, but I found something that makes me take that number with a grain of salt: it does not consider the presence of automated tests. In fact, in the list of 400 bugs analyzed, projects that have no automated tests at all seems to be the norm. I didn’t investigate the list thoroughly, but from a list of 20 projects I randomly picked, only 3 of them had any automated tests at all.
I don’t think automatic tests are a replacement for static type checking, but I do believe that with automatic tests in place you can build very complex systems without missing type safety. It is a pretty subjective opinion though, and certainly not very original. The importance of tests for being Ruby a dynamic language was a recurring topic when I was first exposed to Ruby many years ago.
Better collaboration for large teams working on large codebases
Behind static type checkers, you always find big companies with large development teams pushing for them. For example, Facebook with Flow, Microsoft with Typescript and Stripe with Sorbet. Slack engineering team wrote a piece on their switch to Typescript, Google chose Typescript to rewrite Angular and Shopify was among the main beta testers of Sorbet. All these companies have teams composed of hundreds or thousands of engineers.
Does type checking help engineering teams at this scale? Based on the evidence that they are heavily investing on them, you would say they do. Static types in dynamic languages are a form of enforced documentation. If you modify some method signature, you will be forced to update its type annotations and, thus, the static type checker will determine if this breaks other code. This seems like a good thing when you have hundreds of people working on thousands of interrelated modules.
This is probably the most appealing benefit of types to me. When adding type declarations you are formally declaring what a method receives and outputs: its contract. This is undoubtedly positive and useful. It is something that you can get with proper documentation too, but documentation and code can get out of sync, while enforced type declarations won’t.
If static type checking were free, it would be a no brainer decision. Who doesn’t want better editor support, better documentation and being warned of errors at coding time? Any development team of any size would benefit from that. But we need to make the tenderlove question: at what cost?
First, they represent a new system to learn, new tooling to configure and new code to maintain. Now everyone on the team and future hires will have a new system to learn and deal with.
Second, they represent a huge new dependency on the chosen solution. Would you be comfortable having adopted Flow instead of Typescript given how popular the later is becoming? What about if Typescript came out of fashion because ECMAScript includes a new syntax for optional static type checking in the future? If you have worked with CoffeeScript, you know how painful it is finding yourself with a ton of code written in a language that is suddenly dying quickly. I am not saying any of this is likely, but one thing I am sure is that nobody really knows how the scene will be in a few years, and I believe betting on specific technologies is inherently more risky than doing it on standard platforms.
I think there is a danger of using static type checkers because everyone is talking about them and because Facebook, Google, Slack, and Stripe use them. This is something I also mentioned on my take on SPA frameworks: the needs of mega development teams probably have little to do with yours.
In recent times, I am trying to get better at writing source code documentation. If anything, I understand how types can help to document code but I don’t think types are necessary to have a properly documented codebase.
I am very curious about the future of types in Ruby and how they will impact development tools. The Sorbet team did an outstanding work and they made a wonderful contribution to the Ruby platform.
When looking at specific Sorbet examples, I don’t love how the annotations read interweaved between regular ruby code. To me, these declarations look worse than the equivalent in Crystal or Swift, both statically typed languages that keeps the types noise to a minimum. I wouldn’t be happy if I adding such annotations became the sanctioned way of doing Ruby in the future. I prefer the idea of keeping type information separated from the code (rbi files), which is the approach that seems to be favored by the Ruby core team too.
Finally, I would love to learn more about the tradeoffs and about the experience of companies embracing these systems.