Past, present, and future of Sorbet type syntax

101 PaulHoule 68 5/9/2025, 4:09:45 PM blog.jez.io ↗

Comments (68)

status_quo69 · 3h ago
One thing that I've wondered is why sorbet didn't choose to use the stabby lambda syntax to denote function signatures?

  sig ->(_: MyData) { }
  def self.example(my_data)
    ...
  end
Obviously this opens up a potential can of worms of a dynamic static type system, but it looks sufficiently close enough to just ruby. My opinion is that sorbet doesn't lean into the weirdness of ruby enough, so while it has the potential to be an amazingly productive tool, this is the same community that (mostly) embraces multiple ways of doing things for aesthetic purposes. For example you could get the default values of the lambda above to determine the types of the args by calling the lambda with dummy values and capturing via binding.

Personally having written ruby/rails/c#/etc and having been on a dev productivity team myself, I say: lean into the weird shit and make a dsl for this since that's what it wants to be anyways. People will always complain, especially with ruby/rails.

throwaway346434 · 4h ago
> There was also a project called TypedRuby, largely a passion project of an engineer working at GitHub. After a few weeks of evaluation, it seemed that there were enough bugs in the project that fixing them would involve a near complete rewrite of the project anyways.

There's 6 open bugs and 4 closed ones. This seems like either it's throwing shade or they didn't bother lodging bug reports upstream.

Groxx · 4h ago
Last commit: 2018

Readme:

>Note: TypedRuby is not currently under active development.

I think "brief check, move on" is reasonable, particularly if it doesn't appear to already be at several-year-stable quality.

jez · 3h ago
We were in direct contact with the author, most bugs were reported over email. The project was a hobby project and we did not feel it to be prudent to flood someone’s hobby project with dozens of issues created by a team working at a venture funded startup.
gitroom · 3h ago
man i always bounce between loving ruby's speed to build and wishing for just a bit more built-in safety tbh. you ever feel like adding more types actually slows you down or does it make big teams way less stressed?
bradly · 3h ago
I've been a Ruby dev since 2006 and started using Sorbet fulltime at work about a year ago. After about six months I started wanting it in my personal projects. Not enough to add the dependency, but if Ruby had it built-in I would probably use it. For myself, it makes it easier to work on code I'm not familiar with either because I didn't write it or I wrote it longer than X days ago.

The Sorbet syntax is pretty bad, though. And things go from bad to worse as soon as you get a bit tricky with composition or want to do something like verify the type of an association.

I haven't tried inline RBS comments yet, but the DSL does look more pleasing.

The team in general had mixed views. Some hated it, some liked it, most developers just don't really care–although now the Sorbet advocates are claiming it helps the AI, so now there is that leaning on the Sorbet lever as well.

breckinloggins · 4h ago
> My counter is that when it comes to language design, semantics—what the types mean—are easily 10 times more important than syntax

Sometimes I long for the days before type theory took over programming language research.

mvdtnz · 3h ago
You don't need fancy boy theories to see value in types.
dismalaf · 2h ago
The value in types is telling the compiler to make certain assumptions so it can optimize the output.

There's not a ton of value to be had in something like Typescript or Python types.

dragonwriter · 2h ago
> The value in types is telling the compiler to make certain assumptions so it can optimize the output.

Correctness is more important (in general and as a benefit of type checking) than optimization. Doing the wrong thing fast is easy, but not valuable.

williamdclt · 2h ago
Yet TS is massively popular. So maybe that’s not the only value?
dismalaf · 2h ago
Popular != Good.

Also what's good for enterprise isn't good for everyone.

I get that orgs probably like TS so that newbie devs don't do crazy things in the code, and for more editor hand-holding. But it's not valuable for everyone, if it was actually better than everyone would be using it, not just some people.

usernamed7 · 6h ago
I've been a ruby dev for 15+ years; i'd really love it if ruby adopted a C# similar approach to typing (albeit, more ruby-like and more flexible). It's the most readable, simplest way I would enjoy as a rubyist. Everything else (including sorbet) feels bolted on, cumbersome and cringe. I appreciate the article and how it goes over the constraints; but genuinely sorbet is just not good enough from a DSL standpoint.

Type's can be fun and useful, and i'd love to see them incorporated into ruby in a tasteful way. i don't want it to become a new thing developers are forced to do, but there is a lot of utility from making them more available.

emn13 · 6h ago
While I'm most familiar with C#, and haven't used Ruby professionally for almost a decade now, I think we'd be better off looking at typescript, for at least 3 reasons, probably more.

1. Flowsensitivity: It's a sure thing that in a dynamic language people use coding conventions that fit naturally to the runtime-checked nature of those types. That makes flow-sensitive typing really important.

2. Duck typing: dynamic languages and certainly ruby codebases I knew often use ducktyping. That works really well in something like typescript, including really simple features such as type-intersections and unions, but those features aren't present in C#.

3. Proof by survival: typescript is empirically a huge success. They're doing something right when it comes to retrospectively bolting on static types in a dynamic language. Almost certainly there are more things than I can think of off the top of my head.

Even though I prefer C# to typescript or ruby _personally_ for most tasks, I don't think it's perfect, nor is it likely a good crib-sheet for historically dynamic languages looking to add a bit of static typing - at least, IMHO.

Bit of a tangent, but there was a talk by anders hejlsberg as to why they're porting the TS compiler to Go (and implicitly not C#) - https://www.youtube.com/watch?v=10qowKUW82U - I think it's worth recognizing the kind of stuff that goes into these choices that's inevitably not obvious at first glance. It's not about the "best" lanugage in a vacuum, it's a about the best tool for _your_ job and _your_ team.

Kinrany · 6h ago
Tangent, has C# recovered from nulls being included in all reference types by default?
emn13 · 5h ago
"Recovered" sounds so binary.

I think it's pretty usuable now, but there is scarring. The solution would have been much nicer had it been around from day one; especially surrounding generics and constraints.

It's not _entirely_ sound, nor can it warn about most mistakes when those are in the "here-be-dragons" annotations in generic code.

The flow sensitive bit is quite nice, but not as powerful as in e.g. typescript, and sometimes the differences hurt.

It's got weird gotcha interactions with value-types, for instance but likely not limited to interaction with generics that aren't constrained to struct but _do_ allow nullable usage for ref types.

Support in reflection is present, but it's not a "real" type, and so everything works differently, and hence you'll see that code leveraging reflection that needs to deal with this kind of stuff tends to have special considerations for ref type vs. value-type nullabilty, and it often leaks out into API consumers too - not sure if that's just a practical limitation or a fundamental one, but it's very common anyhow.

There wasn't last I looked code that allowed runtime checking for incorrect nulls in non-nullable marked fields, which is particularly annoying if there's even an iota of not-yet annoted or incorrectly annotated code, including e.g. stuff like deserialization.

Related features like TS Partial<> are missing, and that means that expressing concepts like POCOs that are in the process of being initialized but aren't yet is a real pain; most code that does that in the wild is not typesafe.

Still, if you engage constructively and are willing to massage your patterns and habbits you can surely get like 99% type-checkable code, and that's still a really good help.

neonsunset · 5h ago
> Related features like TS Partial<> are missing, and that means that expressing concepts like POCOs that are in the process of being initialized but aren't yet is a real pain; most code that does that in the wild is not typesafe.

If it's an object, it's as simple as having a static method on a type, like FromA(A value) and then have that static method call the constructor internally after it has assembled the needed state. That's how you'd do it in Rust anyway. There will be a warning (or an error if you elevate those) if a constructor exits not having initialized all fields or properties. Without constructor, you can mark properties as 'required' to prohibit object construction without assignment to them with object initializer syntax too.

emn13 · 2h ago
Yeah, before required properties/fields, C#'s nullability story was quite weak, it's a pretty critical part of making the annotations cover enough of a codebase to really matter. (technically constructors could have done what required does, but that implies _tons_ of duplication and boilerplate if you have a non-trivial amount of such classes, records, structs and properties/fields within them; not really viable).

Typescript's partial can however do more than that - required means you can practically express a type that cannot be instantiated partially (without absurd amounts of boilerplate anyhow), but if you do, you can't _also_ express that same type but partially initialized. There are lots of really boring everyday cases where partial initialization is very practical. Any code that collects various bits of required input but has the ability to set aside and express the intermediate state of that collection of data while it's being collected or in the event that you fail to complete wants something like partial.

E.g. if you're using the most common C# web platform, asp.net core, to map inputs into a typed object, you now are forced to either expression semantically required but not type-system required via some other path. Or, if you use C# required, you must choose between unsafe code that nevertheless allows access to objects that never had those properties initialized, or safe code but then you can't access any of the rest of the input either, which is annoying for error handling.

typescript's type system could on the other hand express the notion that all or even just some of those properties are missing; it's even pretty easy to express the notion of a mapped type wherein all of the _values_ are replaces by strings - or, say, by a result type. And flow-sensitive type analysis means that sometimes you don't even need any kind of extra type checks to "convert" from such a partial type into the fully initialized flavor; that's implicitly deduced simply because once all properties are statically known to be non-null, well, at that point in the code the object _is_ of the fully initialized type.

So yeah, C#'s nullability story is pretty decent really, but that doesn't mean it's perfect either. I think it's important to mention stuff like Partial because sometimes features like this are looked at without considering the context. Most of these features sound neat in isolation, but are also quite useless in isolation. The real value is in how it allows you to express and change programs whilst simultaneously avoiding programmer error. Having a bit of unsafe code here and there isn't the end of the world, nor is a bit of boilerplate. But if your language requires tons of it all over the place, well, then you're more likely to make stupid mistakes and less likely to have the compiler catch them. So how we deal with the intentional inflexibility of non-nullable reference types matters, at least, IMHO.

Also, this isn't intended to imply that typescript is "better". That has even more common holes that are also unfixable given where it came from and the essential nature of so much interop with type-unsafe JS, and a bunch of other challenges. But in order to mitigate those challenges TS implemented various features, and then we're able to talk about what those feature bring to the table and conversely how their absence affects other languages. Nor is "MOAR FEATURE" a free lunch; I'm sure anybody that's played with almost any language with heavy generics has experienced how complicated it can get. IIRC didn't somebody implement DOOM in the TS type system? I mean, when your error messages are literally demonic, understanding the code may take a while ;-).

uticus · 2h ago
neonsunset · 1h ago
This has nothing to do with null analysis. It simply lets you replace an assignment behind an if with an inline expression.
neonsunset · 6h ago
Yes. It will complain if you assign one to something that isn't T?.

For the best experience you may want to add `<WarningsAsErrors>nullable</WarningsAsErrors>` to .csproj file.

usernamed7 · 6h ago
wow i certainly appreciate your perspective and insight as a regular C# developer! My experience was limited to building a unity project for 6 years and learning the differences from Ruby.

Another commenter suggested another language like crystal, and that might actually be what it really needs, a ruby-like alternative.

emn13 · 5h ago
I love building libraries, so having the chance to talk about the gotchas with things like this is a fun chance to reflect on what is and is not possible with the tools we have. I guess my favorite "feature" in C# is how willing they are to improve; and that many of the improvements really matter, especially when accumulated over the years. A C# 13 codebase can be so much nicer than a c# 3 codebase... and faster and more portable too. But nothing's perfect!
uticus · 2h ago
> i'd really love it if ruby adopted a C# similar approach to typing

interesting that you've called out C# as the specific example of a way to approach typing. c# did nothing new with their typing system, instead copied from java and others https://en.wikipedia.org/wiki/Comparison_of_C_Sharp_and_Java...

No comments yet

nightpool · 6h ago
What parts of a C# approach do you think Ruby should adopt? The syntax? I don't think anyone thinks of C# as having a particularly good or noteworthy type syntax or features—anything you can find in C# you can probably find in most other typed languages. I'm curious what specifically you think C#'s typing system is better at than e.g. Typescript, Python or Rust
dismalaf · 6h ago
C#, ew. Crystal (Ruby but statically typed) has nicer type syntax. Or Pony. Or Nim, Odin, and others.
usernamed7 · 6h ago
Hrm, i think this is a good point, It might be something better served by a ruby-like alternative. I'll have to tinker with crystal sometime. Thanks for this comment!
uticus · 4h ago
it's amazing to me that the industry hasn't found a better way to support bits of code interacting with other bits of code properly, than adding an attribute to a bit of code calling it a "type"

...saying this as someone who benefits from it but also rarely uses sub-typing ("poodle" type sub to "dog" type sub to "animal" type) or any sort of the other benefits that are commonly associated with typing. for me the benefit is code checking and code navigation of unfamiliar code bases

xboxnolifes · 3h ago
structural typing? duck typing?
uticus · 2h ago
structural typing is a good step - but afaik there's no way to distinguish between different types that have the same structure but different context - a "truth.right" will be different from a "direction.right", but it's up to the dev to keep that straight.

duck typing is what ruby does without sorbet or rbs, it portrays nothing about how the code bits interact at the boundary. if a "dog" is passed in but a "cat" is expected, things will work just fine until runtime when the cat is asked to bark. (saying as someone who is a big fan of ruby overall)

zhisme · 2h ago
With great power comes great responsibility.

It is developer responsibility to ensure that you will not receive cat during designing stage of your classes. And inheritance is bad, this is also sharp knife. But annotating everywhere

      sig { params(cat: Cat) }
does not improve your design, it just makes noisy and clumsy. I would think that if your code need type annotations, it smells like bad design and should be considered for refactoring.
Trasmatta · 4h ago
When it comes to "Ruby-like and statically typed", Crystal is such an amazing language. I think the design is incredible, but every time I try to use it, I just hit so many issues and things that slow me down. I think it's such a cool language, but every time I try to use it I just end up switching back to Ruby.
eterps · 3h ago
Crystal could have been big if it worked really well in combination with Ruby out of the box (f.i. as an extension language).
Lio · 3h ago
Shout out to the crystalruby[1] project. I’ve not used crystal much myself but I love how easy it is to switch between the two.

1. https://github.com/wouterken/crystalruby

durkie · 4h ago
Same! I reach for crystal when I need something that is going to perform faster than ruby, but man I get my hand slapped constantly when writing it.
smuffinator · 3h ago
I love Crystal. It's what I wish Ruby was. If only the tooling was a bit better
dismalaf · 7h ago
Interesting article, but to me it really defeats the point of Ruby. The hyper-dynamic "everything is an object" in the Smalltalk sense of the definition is much of what makes Ruby great. I hate this idea that Ruby needs to be more like Python or Typescript; if you like those languages use those languages.

I get that a lack of types is a problem for massive organizations but turning dynamic languages into typed languages is a time sink for solo developers and small organizations for zero performance benefit. If I wanted a typed language to build web apps with I'd use Java or something.

Hopefully Matz sticks to his guns and never allows type annotations at the language and VM level.

__float · 31s ago
> Hopefully Matz sticks to his guns and never allows type annotations at the language and VM level.

Why? Just because they _exist_ doesn't mean you'd need to use them.

jbverschoor · 5h ago
It’s all about message passing. Why not allow a compile time check if something respond to a message. Types/interfaces are good. Failing early is a good thing. The earliest you can fail is during editing, and then compile/interpret time.

In Objective-C, you also pass messages to an object, and it could be anything you want. But it would output warnings that an object/class did not respond to a certain message. You could ignore it, but it would always result in a runtime error obviously.

  [someObject missingMethod]
Swift is a much nicer language in terms of typing, and I have started replacing some smaller Ruby scripts with Swift. Mostly out of performance needs though. (Single file swift scripts)

The lack of proper typechecking is one of the main reasons I would not use Ruby, especially in larger teams.

Unittests are fun and all, but they serve a different purpose.

dismalaf · 3h ago
> The lack of proper typechecking is one of the main reasons I would not use Ruby, especially in larger teams.

Cool. I'm using Ruby for a startup because it's 10x more productive for building a webapp versus a compiled language and because I'm solo.

And when I want typing I use something like C++ because I don't want to lose productivity without a major performance boost.

kodablah · 6h ago
> I hate this idea that Ruby needs to be more like Python or Typescript

It's not be more like those, it's be more like helpful, author-friendly programming which is very much Ruby's ethos.

Every time I think about ripping out all of the RBS sig files in my project because I'm tired of maintaining them (I can't use Sorbet for a few reasons), Steep catches a `nil` error ahead of time. Sure we can all say "why didn't you have test coverage?" but ideally you want all help you can get.

PaulHoule · 6h ago
As a Pythoner the most direct value I get out of types is the IDE being smart about autocompletion, so if I'm writing

   with db.session() as session:
      ... use the session ...
I can type session. and the IDE knows what kind of object it is and offers me valid choices. If there's a trouble with it, it's that many Pythonic idioms are too subtle, so I can't fully specify an API like

   collection = db["some_collection"]
   collection.filter(lambda doc: doc["amount"].as_integer() > 500)
   collection.filter({"name": "whatever"})
   collection.filter({"field": lambda f: f["subfield"] = jsonb({"a":1, "b":"2})})
not to mention I'd like to be able to vary what gets returned using the same API as SQLAlchemy so I could write

   collection.filter(..., yield_per=100)
and have the type system know it is returning an iterator that yields iterators that yields rows as opposed to an iterator that yields rows. It is simple, cheap and reusable code to forward a few fetch-control arguments to SQLAlchemy and add some unmarshalling of rows into documents but if I want types to work right I need an API that looks more like Java.
jaredsohn · 2h ago
LLMs can probably help maintain them so that probably could be solved if you start using LLMs more.

This maybe already exists, but it would be nice if RBS or Sorbet had a command you could run that checks that all methods have types and tries to 'fix' anything missing via help from an LLM. You'd still be able to review the changes before committing it, just like with lint autofixing. Also you'd need to set up an LLM API key and be comfortable sharing your code with it.

jimbokun · 4h ago
> Sure we can all say "why didn't you have test coverage?"

Well types are a form of test performed by the compiler.

Lio · 2h ago
I respect this view but I can really see the utility of gradual typing as your project grows and matures.

I also wonder if we’ll eventually be able to pass type information to the JIT. If that helps ruby grow I’m all for it.

dismalaf · 2h ago
> I also wonder if we’ll eventually be able to pass type information to the JIT.

It's not like Ruby doesn't have types and the JIT can't determine them. It just happens at runtime, literally the defining feature of dynamic languages.

Lio · 2h ago
That’s true but it would be useful to upfront specify exactly what’s in an array and exactly how big it will be for the entire life of the array.
zhisme · 3h ago
That's quite interesting why people try to reinvent wheel all over the history.

There are statically typed languages: go, java, rust, even c++. Why one would try to make ruby "look like" statically typed language? It violates the idea of ruby, which dynamically typed language. You can create amazing tools if you think in dynamic way (duck-typing, meta-programming, runtime definitions). There are messages that you send to objects not statically typed bits and procedures (which the way of thinking for statically typed languages and procedural programming). You even have in your toolbox methods like (to_int, to_str, to_ary), that already do type-check strictly.

When you add types to ruby what are your benefits? It becomes faster language? No, it becomes even slower. It becomes more readable? Alas no. It becomes type safe? Yet no. It becomes more verbose with this type annotation everywhere making it look like nightmare. Why should one use hammer with self-tapping screw? Use screwdriver for that kind of activity.

phoronixrly · 3h ago
Ruby is a language that grants you lots of freedom. Including often the freedom to shoot your own foot. Bolting on types to it is something I expect to be possible in Ruby and completely in tune with one or both my previous statements.
zhisme · 2h ago

  Ruby is a language that grants you lots of freedom.
Nobody can argue that. With freedom we can do anything, but anyone wondering why in first place?

What about wisdom and pragmatic approach? If you ever need types why not choose typed language for your case, but instead re-implementing something and wasting so much time for work that has no real use. Why not use c-bindings for typechecks, this feature is already available in ruby *rb_obj_is_kind_of(value, rb_cString);* and has no cost. Is it wise? I don't think so, I would be shocked if I saw such code in production.

We care so much about environment and climate changes, but think it is ok to waste resources for such activity. It is not research task. People really advocate to use in real-world applications such thing as sorbet. DHH even removed typescript from his frontend libraries, and this types is like kindergarten for some homemade rack/hanami/dryrb application that will server 2 people in production (creator of this stuff and random visitor). Maybe someone could tell me why we need such thing in ruby ecosystem like rbs/sorbet library. I'm really wondering. Not because ruby grants us freedom and we can do that. That's not an answer. We can do enormous count of absurd things. But the question is why?

phoronixrly · 53m ago
Woah there, you're making a straw man here but I'll bite. I'll preface it with the fact that I also think types are against the very nature of Ruby as I know and practice it.

But, why not choose a proprer statically typed language? Because you've inherited a mastodon of a legacy app that started simple, then matured with thick layers of features. How can you wrangle this? Oh just rewrite it in Rust. Or you could seek an iterative approach and introduce tools to help you. Like bolted-on types.

johnfn · 2h ago
> Why one would try to make ruby "look like" statically typed language? It violates the idea of ruby, which dynamically typed language.

People said the exact same things when Typescript was first being released. I think history has proven all those people decisively wrong. I think you'd need a compelling reason that Ruby has some je ne sais quoi that Javascript doesn't in order to support your point.

zhisme · 2h ago
The thing is I don't want ruby to meet the fate of javascript. Javascript community is much bigger than ruby will ever be. They had to manage so many people writing too many code, and invented tool for them so they can contribute without making them learn completely new programming language. Easier to jump in. And I should mention typescript types is much better than what we have in rbs/sorbet. They have types inside language, without annotation like what we have separate sig DSL in ruby.

We should invent new programming language typed_ruby and give him separate life, and then history will prove us whether it should have been implemented in the first place. But not try to push it into stdlib and persuade everyone else that it is the right way to go.

monooso · 2h ago
Plenty of people dislike TypeScript, and don't feel the supposed benefits outweigh the costs.
airstrike · 2h ago
Typescript's success has much more to do with JavaScript being the only choice we have in the browser than it has to do with particular programming language design that we must seek to replicate.
uticus · 2h ago
> It becomes more verbose with this type annotation everywhere making it look like nightmare.

this is part of the appeal of the "header file approach" a la RBS, as mentioned in the article https://blog.jez.io/history-of-sorbet-syntax/#the-header-fil...