A couple of weeks ago I landed a small addition to Sorbet
T.must_because
. Out of context the method name sounds a bit odd but it's been
super useful to have around.
Since I started using Sorbet, it has had a plain
T.must
method, which narrows a nil-able type:
int = T.must(int_or_nil)
But these were infuriating to me:
The error message T.must
raises when a
nil
is passed is simply:
TypeError: Passed `nil` into T.must
What I have wanted for a long time is a place to provide
context. With T.must_because
we have nice
friendly and concise way of doing just that:
sig {params(list: T::Array[String]).returns(String)}
def human_readable_second_item(list)
if list.size < 2
return "List not long enough"
end
item = T.must_because(list[1]) {'we check the size above'}
"Second item: #{item}"
end
Despite not having worked with C++ or Sorbet's test suite before, the process was pretty straight forward to land the feature. Check out the pull request on Github!
The T.must_because
method is gaining steam
internally at Stripe and I recommend giving it a try in other
codebases. I personally have no reason to use
T.must
ever again. Context, especially when you're
escaping the type system, is always good.
Sorbet is all about those integration tests. Most of the tests for Sorbet are on the level of textual output and uses a pseudo language for making assertions about what errors are expected. The tests were really cool being so tangible, definitely a good way to avoid stressing on internal details.
All Ruby methods take an implicit block so a block
doesn't add performance overhead in itself.
In evaluating call signatures for this method I learned from
the Sorbet Slack channel that the
{"msg"}
syntax was optimal. Coming
from JavaScript where function allocations need to be
considered in performance-sensitive code, this was
surprising. This definitely seems like a place where the
Sorbet compiler would be able to optimize to an extent such
that it would become perf-sensitive.