I used statically typed languages and liked the extra safety but I also really like Ruby for how elegant it is and the freedom it gives me. Will I regret adopting types?
Will gradual typing be supported long term or is it a fad? Will this be an abandoned investment? If I decide to add it, which solution should I pick, battle tested Sorbet or core team endorsed RBS?
This article has an interactive component for evaluation. If you’re here just for that and want to skip the intro, you can jump to the evaluation section.
I’ve also been wondering about the correct choice. I’ve been playing with Rust on the side and really enjoying it. On more than one occasion when debugging a Ruby issue I’ve thought “this would have been a compile time error in Rust”. On the other hand I really enjoy beautifully written Ruby. IMO, elegance and readability of well crafted Ruby code is hard to match in almost any other language. On most real life projects, the speed of development is easily worth an occasional corner case bug with an unexpected
nil value … except when it isn’t.
Beyond reading posts and comments on the topic I’ve also done the exercise of fully adding both Sorbet and RBS to a side project1.
Quickly, of the top of your head, which approach is older, static or dynamic typing?
FORTRAN is considered the worlds oldest widely adopted high level programming language and it first appeared in 1957. It is statically typed, so static typing is older. However, Lisp first appeared in 1960 and it was the first dynamically typed programming language. Notice how close in time they are, just 3 years apart, over 6 decades ago.
Since then, a lot of digital ink has been spilled over which approach is better. If you google for “static vs dynamic typing” you will find a rabbit hole of articles arguing for one or the other with excellent points on both sides. Academic literature is no more conclusive 2345. The effects that do show up in studies are usually small and the authors notice that they are likely to be dominated by other factors, like how experienced the team is or what other software methodologies are used to prevent bugs.
There are many dimensions that make up a measure of quality software development and not all of them are along the same axis. As evidenced by many teams successfully using both static and dynamic languages to build high quality software, it’s possible to thrive with both approaches.
And that is why it is safe to conclude that whether you would benefit from adopting gradual typing in your Ruby project is going to depend on a number of factors, i.e. you should do a cost vs benefit analysis for your particular project with considerations of alternative solutions.
If you are enjoying my work, consider subscribing to not miss future articles:
For Ruby projects in particular there are two main benefits: reduction of type related bugs and improved tooling. Static typing systems in other languages carry other benefits, like increased speed from ahead of time compiler optimisations. However we don’t have that available in Ruby, at least not yet6.
I’ve identified 5 main factors which consistently appear in discussions and recommendations. This part of the article has some light interactivity. Each factor has simple scoring and you can click on them to vote. At the bottom of the post it will automatically tally up your result and provide some recommendations.
If you are considering adding types to an existing project, you already have some data you can examine to estimate what proportion of bugs it would have likely caught. In Ruby, a typo in the name of constant will result in a
NameError. A valid, but incorrect type usually results in a
NoMethodError. Most often it will be a
nil where a non nil object is expected. Less likely, in the case where you have the correct type but are calling the method with a wrong number of arguments you will get an
ArgumentError. With that you can:
- Search your bug tracker for occurrences of
- Search bug tickets in your project management software for the same snippets. Try similar phrases.
- Search your git commit messages for occurrences:
git log --grep="NameError". If your team has some git commit message convention you can use, that will help a lot.
If you are considering it for a new project then remember that often the best prediction of the future is assuming that the past will repeat. Do the same analysis on a previous project, preferably done by the same team.
How many errors that would be caught by type checking are you finding:
- 0 pt : No or almost no errors.
- 1 pt : Moderate amounts, regularly occurring but not among the most frequents errors.
- 2 pt : High amounts and among our most frequent errors.
How much benefit you gain from tooling that can take advantage of types is going to depend on what editors you and rest of your team prefer. Most of the community now uses full featured editors but Ruby is expressive enough that one can develop effectively in a more bare bones editor, and a lot of people still do that. Also, with the adoption of LSP (Language Server Protocol) it’s very likely that almost all of the editors can take advantage of a language server, it’s just that for some editors it will be easier to get it working:
- Visual Studio Code has an official extension for both Sorbet and Steep+RBS.
- RubyMine has built in support for Sorbet.
- SublimeText has official docs on how to use Sorbet LSP.
Survey your team for their setup and tally how many will be able to take advantage of improved LSP features with their current setup. It is unlikely that you will get people to change their setup and be happy with it.
How many people in your team will be able to take advantage of improved editor tools with their current dev setup?
- 0 pt : Almost none, less than 20% of the team.
- 1 pt : Some developers, 20-80% of the team.
- 2 pt : Almost everyone, 80%+ of the team.
All type checkers still struggle a lot with meta-programming. Stripe, the creators of Sorbet have seen a decline in meta-programming 7 with the adoption of Sorbet and the team is happy with that. The affinity to using meta-programming in Ruby is going to vary a lot between different development teams and it’s a very important factor in deciding whether to adopt gradual typing as the two work against each other.
It’s both important to estimate how much meta programming you are currently using and what is the team’s preference. Here I am talking only about meta-programming in your own code. It’s likely you are using Rails and it has a lot of meta-programming under the hood but that is hidden from you. There are repositories of RBI and RBS files for standard library and popular gems and the cost of maintaining them is amortised across the community, i.e. you don’t have to bear that cost.
How much meta-programming are you using?
- 0 pt : We make extensive use of meta-programming and will continue doing so.
- 1 pt : Some meta-programming and we intend to continue using it.
- 2 pt : None at all and we intend to keep it that way, or we have some but want to eliminate it.
When collecting comments from forum and blog posts I noticed that most of the excitement is coming from people working on very large codebases. All of the tooling and evangelism for Ruby gradual typing is coming from large companies with very large Ruby codebases. Even more importantly, almost all of the related development is due to investments by those companies. Matz has shown, at best, reluctant support for typed Ruby. It is reasonable to expect that most of the improvements will be geared towards making the tooling work better on large codebases worked on by a lot of developers. An often cited main benefit is the ability to accurately jump to definition. So instead of measuring the size of the codebase, because we have no data on this, let’s frame it in the context of jumping to definition.
How much would you benefit from improved “Jump to definition” editor integration?
- 0 pt : Not at all, I mostly already know where something is defined or my existing setup works (e.g. solargraph).
- 1 pt : I would get some mild benefit. It happens regularly but not frequently that I have trouble finding the definition of a method or a constant.
- 2 pt : It would be a game changer, this is a frequent stumbling block for me when working.
Last but not least, this is a change that will directly affect how people work and it’s a topic on which developers often have strong opinions. Don’t lie to yourself, even if you have great arguments for adopting it, forcing this on people who really dislike it will certainly have a negative effect on their performance. This is something that will be woven through their day to day work and impossible to ignore. We shouldn’t lose sight of the fact that software is made by humans.
How many people in your team are excited to try adopting types in your project?
- 0 pt : Almost none, less than 20% of the team.
- 1 pt : Some developers, 20-80% of the team.
- 2 pt : Almost everyone, 80%+ of the team.
You haven’t answered any questions yet, you can answer by clicking on the answers above.
Here is the table for evaluating results which was created by a very scientific process of me thinking very hard about it! So, yes, if you disagree with the scale, I definitely invite you to think very hard about it and modify it. This is also part of the evaluation process. ;)
You don’t seem to be in a good position to benefit from gradual types. It’s likely to be an uphill battle with little to show for it. I would suggest that instead you look into alternative approaches: increasing test coverage, adopting different kinds of linters, refactoring problematic parts of the codebase.
I wrote in more details about that in: “Preventing bugs in Ruby: tools of the trade”.
It’s unclear if you’d benefit from adopting types. I would suggest doing an experiment by either adding it to part of your main project or to a smaller non-main project, a side project or an internal company facing project.
You seem to be in a great position to benefit from adopting types. See the next paragraph for advice on deciding between Sorbet and RBS.
If your evaluation resulted in you deciding to adopt gradual typing then one last question to answer is which tool to use?
One would expect that the decision can easily be postponed because it is theoretically possible to automatically translate one into the other. After all, they should ultimately both contain the same information. However, in practice, this work has not been high on anyone’s list of priorities. Shopify worked internally on an RBS to RBI automatic transpiler but it was abandoned in January 2023. From what I’ve gathered it was because Shopify itself doesn’t have any benefit from it. Also, the problem seems to be harder than one might expect and we are unlikely to get such a tool until some sufficiently large company decides to switch from RBS to RBI.
So, at the moment, you are better off picking one and sticking with it. If you have decided to adopt gradual typing and this is the last decision that you need to make, the most important difference is on how you as a team feel about inline signatures?
Some developers find Sorbet signatures to be a source of useful information due to them being very compact. They lack the semantic information that you usually find in, for example, YARD comments. However, if majority of the team has this stance, that is a big reason for choosing Sorbet with the default setup of inline signatures. This benefit is absent in the case of RBS or Sorbet in separate RBI files. This is going to be very subjective and you are unlikely to change someone’s mind on it. Software is made by people and people’s preferences are going to affect their productivity. So, it matters.
RBI files are just regular Ruby without method bodies and are therefore more verbose than RBS which is a separate syntax designed just for signatures. This all means that if you are going to exclusively write signatures in a separate file, you will be more efficient using RBS and you should choose RBS. If you like inline signatures then Sorbet seems like the better choice at the moment.
Try using both on a few key files in the project and make this decision based on what the majority of people on your team prefer.
Whatever you pick in the end, please write about your experience so we can learn as a community!
Have you found this evaluation useful? Do you think there are criteria that I missed? Please let me know in the comments below.
I wrote about the details of my experiment and the results in Experiment: Fully adding Sorbet and RBS to a small project ↩ ↩2
The article “An empirical comparison of C, C++, Java, Perl, Python, Rexx, and Tcl” is a bit older (from 2000) so it should be taken with a grain salt as we had a lot of development since then, especially in static language tooling. It examines 80 implementations of the same problem in different dynamic and static languages. It finds that the dynamic languages were more productive, however, even more importantly, it finds that the difference between languages was smaller than differences between programmers working in the same language. In other words, who is programming mattered more than with what. ↩
The article “An empirical study on the impact of static typing on software maintainability” compares speed of development and a few other factors between Java as static and Groovy as dynamic representative. The experiment has 33 participants solving 9 different tasks. The paper has a few interesting findings but the main ones are: Java being more effective for finding type errors and there being no sigificant difference in the tasks of findings semantic errors. A very interesting finding is that a likely reason for reduced time in fixing type errors is reduced time navigating the codebase. ↩
The article “Static vs. Dynamic Type Systems: An Empirical Study About the Relationship between Type Casts and Development Time” has 21 subjects solving programming tasks in Java and Groovy. To quote the paper: “The result of the study is, that the dynamically typed group solved the complete programming tasks significantly faster for most tasks - but that for larger tasks with a higher number of type casts no significant difference could be found.” ↩
The article “A Large Scale Study of Programming Languages and Code Quality in Github” examines 729 projects from Github and estimates code quality by examining commit history looking for commits that address defects. It finds that static typing is better than dynamic. However, the authors are quick to point out: “It is worth noting that these modest effects arising from language design are overwhelmingly dominated by the process factors such as project size, team size, and commit size.” ↩
Stripe worked on an ahead of time compiler which would have taken advantage of types and you can even see an unfinished version in the source code but it was abandonded due to not being valuable enough internally. You can hear more about it in this Changelog podcast episode, starting at 13:45 ↩
This changelog episode has a very interesting discussion specifically about metraprogramming, starting at 19:57. In short, heavy use of metaprogramming in Ruby goes against the effort of introducing a type checker like Sorbet. ↩
Shopify wrote about it in Adopting Sorbet at Scale. Details of experiment results start under the subtitle “Benefits Realized, Even at typed: false”. Their results are somewhat in line with what I found in my experiment. ↩