One feature that Sorbet doesn’t haveYet. The biggest limitation is just that Sorbet’s
approach to type inference is designed to run fast and be simple to
understand, sometimes sacrificing power.
… but actually Sorbet already has these types
internally 😅 It’s just that it doesn’t have syntax for people to write
them in type annotations. And lo, it’s because
they’re buggy, but for the things where Sorbet needs to use them
internally we can intentionally work around the known bugs, so it hasn’t
been worth the pain to fix.
but gets requested frequently is support for literal
string and symbol types. Something like
T.any(:left, :right)
, which is a type that allows either
the symbol literal :left
or :right
, but no
other Symbol
s much less other types of values. The closest
that Sorbet has to this right now is typed enums:
class LeftOrRight < T::Enum
do
enums Left = new
Right = new
end
end
TypeScript, Flow, and Mypy all have literal types. You probably have felt yourself wanting this. I don’t really have to explain why they’re nice. But I’ll do it anyways, just to prove that I hear you.
👎
T::Enum
cannot be combined in ad hoc unions.
That’s a fancy way of saying we’d like to be able to write
T.any(:left, :right)
in any type annotation, without first
having to pre-declare the new union type to the world. I spoke at length
about how the existence of ad hoc union types make handling exceptional
conditions more pleasant than
checked exceptions, so I’m right there with you in appreciating that
feature.
👎 T::Enum
is verbose.
Even if you wanted to pre-declare the enum type. Consider:
LeftOrRight = T.type_alias {T.any(:left, :right)}
Boom. One line, no boilerplate. Wouldn’t that be nice?
👎 It’s hard
to have one T::Enum
be a subset of another.
This comes up so frequently that there’s an FAQ entry about it. The answer is yet more verbosity and boilerplate.
So I hear you. But I wanted to say a few things in defense of
T::Enum
, because I think that despite how nice it might be
to have literal types (and again, we may yet build them one day), there
are still a lot of points in favor of T::Enum
as
it exists today.
🚀 Every IDE
feature Sorbet supports works for T::Enum
.
T::Enum
s are just normal constants. Sorbet supports
finding all constant references, renaming constants, autocompleting
constant names, jumping to a constant’s definition, hovering over a
constant to see its documentation comment. Also all of those features
work on both the enum class itself and each individual enum value.
We could maybe support completion for symbol literals in limited circumstances, but it would be the first of its kind in Sorbet. Same goes for rename, and maybe find all references. Jump to Definition I guess would want to jump not to the actual definition, but rather to the signature that specified the literal type? It’s weird.
🙊
T::Enum
guards against basically all typos.
Even in # typed: false
files! Even when calling methods
that take don’t have signatures, or that have loose signatures like
Object
! Incidentally, this is basically the same reason why
find all references can work so well.
🤝 It requires being intentional.
Code gets out of hand really quickly when people try to cutely interpolate strings into other strings that hold meaning. I’d much rather deal with this:
= [left_or_right, up_or_down] direction
than this:
= "#{left_or_right}__#{up_or_down}" direction
If you try to do this with T::Enum
you get strings that
look like:
'#<LeftOrRight::Left>__#<UpOrDown::Up>'
which confuses people, so they ask how to do the thing they’re trying
to do, which is a perfect opportunity to talk them down from that cliff.
If people decide that yes, this really is the API we need, we can be
intentional about it with .serialize
:
= "#{left_or_right.serialize}__#{up_or_down.serialize}" direction
🕵️ It’s easy to search for.
This is a small one, but I’ll mention it anyways. It’s quick to
search the Sorbet docs for T::Enum
and get to the right
page. It’s similarly easy to find examples of it being used in a given
codebase, to learn from real code. There’s no unique piece of syntax in
T.any(:left, :right)
that is a surefire thing to search
for.