[HN Gopher] Past, present, and future of Sorbet type syntax
___________________________________________________________________
Past, present, and future of Sorbet type syntax
Author : PaulHoule
Score : 94 points
Date : 2025-05-09 16:09 UTC (6 hours ago)
(HTM) web link (blog.jez.io)
(TXT) w3m dump (blog.jez.io)
| dismalaf wrote:
| 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.
| kodablah wrote:
| > 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 wrote:
| 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.
| jimbokun wrote:
| > Sure we can all say "why didn't you have test coverage?"
|
| Well types are a form of test performed by the compiler.
| jaredsohn wrote:
| 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.
| jbverschoor wrote:
| 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 wrote:
| > 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.
| Lio wrote:
| 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 wrote:
| > 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 wrote:
| 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.
| usernamed7 wrote:
| 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.
| dismalaf wrote:
| C#, ew. Crystal (Ruby but statically typed) has nicer type
| syntax. Or Pony. Or Nim, Odin, and others.
| usernamed7 wrote:
| 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!
| emn13 wrote:
| 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 wrote:
| Tangent, has C# recovered from nulls being included in all
| reference types by default?
| neonsunset wrote:
| 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.
| emn13 wrote:
| "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 wrote:
| > 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 wrote:
| 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 wrote:
| nope, still recovering:
| https://github.com/dotnet/core/blob/main/release-
| notes/10.0/...
| neonsunset wrote:
| This has nothing to do with null analysis. It simply lets
| you replace an assignment behind an if with an inline
| expression.
| usernamed7 wrote:
| 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 wrote:
| 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!
| nightpool wrote:
| 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
| uticus wrote:
| > 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...
| throwaway346434 wrote:
| > 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 wrote:
| 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 wrote:
| 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.
| Trasmatta wrote:
| 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.
| durkie wrote:
| 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 wrote:
| I love Crystal. It's what I wish Ruby was. If only the tooling
| was a bit better
| eterps wrote:
| 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 wrote:
| 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
| breckinloggins wrote:
| > 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 wrote:
| You don't need fancy boy theories to see value in types.
| dismalaf wrote:
| 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 wrote:
| > 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 wrote:
| Yet TS is massively popular. So maybe that's not the only
| value?
| dismalaf wrote:
| 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.
| uticus wrote:
| 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 wrote:
| structural typing? duck typing?
| uticus wrote:
| 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 wrote:
| 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.
| gitroom wrote:
| 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 wrote:
| 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.
| zhisme wrote:
| 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 wrote:
| 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 wrote:
| 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_?
| uticus wrote:
| > 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...
| johnfn wrote:
| > 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.
| airstrike wrote:
| 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.
| monooso wrote:
| Plenty of people dislike TypeScript, and don't feel the
| supposed benefits outweigh the costs.
| zhisme wrote:
| 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.
| status_quo69 wrote:
| 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.
___________________________________________________________________
(page generated 2025-05-09 23:00 UTC)