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.

I have mixed feelings about using types in Ruby or in Javascript:

  • 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.

Last year I did a fair amount of C# with Rider, and I was surprised how little difference I found when it comes to refactoring and autocompletion, compared to my work with Ruby and Javascript. Sure that types help, but it is also amazing how far a sophisticated IDE can go without them. A significant percentage of my code editing activity is renaming things and extracting methods and Rubymine handles this perfectly, although I am sure types will make for even better refactoring support.

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.

Robustness

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

Fewer bugs

After many years of writing Ruby and Javascript, I never identified type-checking as a source of brittleness in the apps I have worked on. I have worked with Java and C# quite a bit too, so I am familiar with typed languages. Types aren’t just in my list of factors affecting code quality.

I was really curious about this article that empirically tested how Javascript type checkers would reduce bugs by a 15%:

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.

This makes sense to me. If you are not using automated tests to exercise you dynamically typed code, I am sure a static type checker can help immensely. Indeed, I have experienced the brittleness of large Javascript apps without proper unit tests. I have never experienced this with Ruby, and I believe it is because I have never worked on a large ruby codebase that wasn’t, more or less, properly tested.

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.

Better documentation

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.

Costs

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.

And third, on the more subjective side, I enjoy not having to care about type declarations when writing Ruby and Javascript. I think that’s a feature, not a problem to fix. To me, code is more pleasant to write, and I really care about happiness at coding time. I think writing textual documentation with light conventions matches my taste better than a formal syntax. Last year, whenever I went from C# to Ruby, I found myself double-appreciating this. Having to declare types is not something I am looking forward to.

Conclusions

To me, static type checking is a form of ceremony. The bigger a team is, the more ceremony needed. Ceremony always has a cost, and you want to keep it to the minimum necessary, so I don’t think static type checkers are a no brainer like suggested by many articles in the form of ‘If you are not using typescript instead of Javascript, you are wrong’. I think the opposite should be the default approach. Use languages are they are meant to be used and alter them only if your requirements really justify it.

When writing Ruby and Javascript, I consciously enjoy not having to care about type declarations. Having to write types back would really have to be justified for me. I believe this is a feature of dynamic languages, not a problem that you need to solve if you are a serious™ developer.

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.