Sorbet: Adventures in Gradual Typing
03 Aug 2020 by Dave
Ruby. The language designed for programmer happiness has lost some of its shine in recent years. Some of it is just fashion, but even in our code base, which doesn’t have millions of lines of code, the limitations of a highly dynamic language can be painful at times.
Enter Sorbet, which adds static type-checking to Ruby. Gradually. What I like most about statically typed languages is getting immediate feedback from my editor when I do something wrong, instead of having to run tests like in Ruby. I’ve recently installed an unofficial Sorbet extension for VS Code, and while it has some issues, it’s really great for flow (for the limited coding I still do) when it works well.
Now, what I like most about the dynamic nature of Ruby is that I can bend it to my will. It’s the language that showed me how much better declarative code can be than procedural - I don’t want to specify how something should get done, I just want to describe what should get done in the form of a DSL that takes care of the details. Sorbet seeks to bring together some of the benefits of static and dynamic languages. But how much does it have to compromise to get there?
We started using Sorbet about 7 months ago. After a period of not running the checks (we got sidetracked by a certain global event), I recently ran it and the dead code detection caught a bug that ended up on production. (Luckily it was only the groundwork for another, unreleased feature). This has renewed my interest in furthering our adoption.
It can be a slog though. Gradual typing takes some getting used to. And the tool itself needs some maturing. It’s fast though! Apart from that, the Ruby stdlib and some popular gems have type definition files (known as RBI files) that are maintained by the Sorbet team and the community, and you’ll sometimes run into incomplete or incorrect type definitions. (the correct solution is to submit a PR, but we’ll get to that in a bit).
The future of Ruby?
I think Sorbet can breathe new life into tired Ruby code bases and makes large scale refactoring easier. It also seems like the Sorbet team is working with the Ruby team to bring something similar to Ruby 3 (optional, of course).
Even if you never write a single type signature in your own code, you can still benefit from gradual typing. I believe the greatest benefits will come from having accurate method signatures for the stdlib, and for gem authors to start creating RBI files (or for the community to step in and help out). Imagine using a gem for the first time, and not having to constantly refer back to the docs to see if you’re passing the right arguments, or if the method you’re looking for even exists, while your editor provides an intellisense-like view of available methods. For Rails users, you can generate RBI files for your app, using the sorbet-rails gem, so you’ll immediately see similar benefits.
All of this will require the community to adopt it though. The concept of a gradually typed Ruby will succeed if the community believes it will succeed. And the more adoption there is, the more improvements we’ll see. It comes down to network effects, and from where I’m standing it’s not guaranteed to succeed. So please, if you enjoy Ruby as much as I do and think this would be a valuable addition, remember to contribute back your PRs.
Conclusion
Sorbet makes some of the “fun” parts of Ruby more difficult to use - method_missing can no longer be used to create a completely dynamic interface, unless you create corresponding RBI for all the possible “methods” that could be called. I’m happy to give that up if it means removing the negative fun involved with never being certain what a piece of code is really going to do when I read it. Best of all, because it’s gradual I still get to keep my DSLs.
Just as I was writing this, the Square team officially announced their collaboration with the Ruby team to bring type definition files to Ruby 3, which will also be compatible with Sorbet. It’s a good time to be a Rubyist!