9 ms·
Sorbet: Stripe's Type Checker for Ruby
- bbrree66 5y ago
- jez 5y agoHey! I wrote this article. If you have any questions about Sorbet or Stripe, please don’t hesitate to ask!
- cmer 5y agoI added Sorbet to my codebase right after reading the article, but it seems to be expecting that I annotate every single one of my gems. Is this accurate? Is there a way around this?
- jez 5y agoSomewhat. They don't all need super specific types for every method they've defined, but Sorbet does at least need to know all the classes, modules, and constants in use in your codebase, whether those come from code you've written or code inside gems. But there's tooling (first-party and third-party) that will either download or generate RBI files defining constants that come from gems. `srb init` is the first party solution, and Shopify's `tapioca` gem is the most popular third-party solution[1]. Unfortunately, because Ruby doesn't have import statements at the top of every file, Sorbet can't just do something like silently treat unknown imports as not having a type (like TypeScript and Flow can do), because then it would never be able to tell between "exists but unknown" vs "typo; does not exist" for constant definitions. This definitely makes the adoption process a little tricker compared to other languages, but it's generally a one-time thing once you've got the tooling set up. Also if you're ever having trouble getting the tooling to work, there's a lot of people chatting about Sorbet daily at https://sorbet.org/slack https://sorbet.org/slack [1] https://github.com/Shopify/tapioca https://github.com/Shopify/tapioca
- felipeccastro 5y agoI tried adding this to a new Rails project with no luck. Is there a sample Rails app with Sorbet fully configured (i.e. most gems typed) available on github for reference?
- jez 5y agoUnfortunately I don't know of an example repo, but I do know that most projects (except Stripe) who use Sorbet use it with Rails. There's a #rails channel on Slack—maybe you'd like to try asking there! https://sorbet.org/slack https://sorbet.org/slack
- lasvad 5y agoWith Ruby 3 releasing with RBS, for new projects, whats the current advised path? Native RBS or Sorbet? Can they co-exist and if so, is there a point to using both? Sorbet is something I've been interested in using for a couple years and finally got a round to actually trying it out. I tried to use Sorbet with ruby 3.1.1 but unfortunately it didn't "just work" which I think is crucial for mass adoption. I want to give the benefit of the doubt and say its my local env that causing issues with Sorbet but in a fresh `rails new test_app --api` project, I'd expect `srb init` to work without errors... maybe I need to give it another go, curious on your thoughts above tho! :)
- jez 5y agoWe discovered a bug in `srb init` for Ruby 3.1 recently that a teammate of mine is working on fixing at the moment. It's likely that if you tried again in a few days it'll have been fixed. Sorry about that, totally agree that the out-of-box experience should just work. I wrote up an FAQ about the state of Ruby 3 and RBS here: https://sorbet.org/docs/faq#when-ruby-3-gets-types-what-will-the-migration-plan-look-like https://sorbet.org/docs/faq#when-ruby-3-gets-types-what-will... The tl;dr is that RBI files (not RBS files) will probably always be the preferred way to declare types for third party code (because it will always support exactly the same set of features that Sorbet does). We have some people in the community look into teaching Sorbet to read the RBS format, but the existing parsers for RBS files are written in Ruby and are very slow, and there are some ambiguities in the spec that make writing a third party parser that compiles to native code tricky. You can see an attempt to write a fast RBS parser in C++ here[1], but again given that RBI files do everything we need them to right now and we have other features people are asking us for, we haven't prioritized RBS support incredibly highly. Sorbet works completely fine without RBS files! [1] https://github.com/Shopify/rbs_parser https://github.com/Shopify/rbs_parser
- sankha93 5y agoHey, nice work with Sorbet! I am one of the grad students who worked on RDL, one of the early research projects related to Ruby type systems. What are the next set of challenges that a tool like Sorbet needs to solve? I see you mentioned meta-programming in the blog post, is that something that is handled well by Sorbet? Sorry if this is already handled, I haven't been up to date with the latest features of Sorbet.
- jez 5y agoThanks for your work on RDL! The post didn't mention it, but Sorbet still owes most of its type definitions for the Ruby standard library to RDL's original annotations. We just borrowed them and changed the syntax. Our general approach to metaprogramming at the moment has been two-fold: - Use ahead-of-time code generation powered either by runtime reflection or ad-hoc static analysis to generate RBI files declaring things that have been metaprogrammed. - Build type system features, errors, and autocorrects that encourage people to structure their code in ways that doesn't require metaprogramming to solve. Metaprogramming is definitely still a sticking point, but the existing solutions work ~okay and the rest of the upside Sorbet provides make it worthwile to power through. Next challenges: - Make it faster. While the post was talking about how fast it is, it wasn't telling the whole truth. Turns out some type checking operations in a 15 million line codebase are still slow, and we're working on making those faster. - Add more IDE features. At the beginning of this year I put a lot of work into making Sorbet's parser more tolerant of syntax errors, which helps things like autocompletion work better. We also want to make more code actions, autocorrects, and refactoring tools, to bring Ruby in line with what you'd expect from other typed languages in the IDE experience - Add more type system features. Shapes and tuples are a huge unimplemented feature still, and people ask about it all the time. There are a handful of other type system features (happy to list them if you're curious) that would also let people write idiomatic Ruby and still have good typing. Lots left to do!
- mwint 5y agoI write a lot of Ruby for work, but I’m not sure what “shapes” are - do you have any good reference where I could start reading?
- burlesona 5y ago`srb init` has had a lot of problems since Ruby 3.x, and while I haven't tried in a few months it looks like there's recent issues that it still doesn't work (https://github.com/sorbet/sorbet/issues/5332 https://github.com/sorbet/sorbet/issues/5332). Is the advice just to use Tapioca instead of `sub init` at this point?
- jez 5y agoHoping to have this specific issue fixed either today (or by the end of the week at the latest). So sorry for the delay in getting around to this!
- jez 5y agoPR from my teammate Trevor is was just posted which should hopefully fix the issue you linked: https://github.com/sorbet/sorbet/pull/5533 https://github.com/sorbet/sorbet/pull/5533
- chucke 5y agoWhen is RBS support coming?
- vhodges 5y agoWhat is the status of the AOT compiler? Still moving forward? Stuck for lack of resources? Release planned soon? :).
- jez 5y agoUnfortunately while we've developed the Sorbet Compiler in the open, we've mostly focused on internal-only use cases. There are other exciting efforts to improve Ruby performance though! You might be curious to read more about YJIT, the JIT compiler that was recently merged into Ruby 3.1—it’s being primarily developed by a team at Shopify where Ruby performance is the highest priority.
- vhodges 5y agoNo worries and thanks, I'll check it out!
- willlll 5y agoOverall sorbet has been good and useful, so thank you. But you just have to be careful when you're not using the same version of ruby that stripe uses. I recently got hit by a bug: # typed: strict extend T::Sig sig {params(x: Integer, y: String).void} def run(x:, y:) puts(x + 1, y) end args = { x: 1, y: "Hi" } run args type checks just fine in sorbet, but is an error in ruby 3+. It works though (with errors) in 2.7 and lower which is what Stripe uses from what I gather.
- jez 5y agoNote that Shopify is much better about keeping up with regular Ruby updates (Stripe takes a pretty conservative approach there), and they're using it with Ruby 3+ with no problem. You're right that Sorbet does not yet catch these bugs yet, and we'll likely get around to it in the future. It's being tracked in this issue[1]. We haven't quite prioritized this yet partly because it doesn't actually prevent you from using Sorbet in a Ruby 3 codebase, it just won't report all the errors it could (e.g., Sorbet allows using `T.untyped`, which can also cause runtime errors to go unreported). Now that you've jogged my memory, there used to be some weirdness in our internal representation for method calls that made implementing this feature tricky. But we've since refactored some internal data structures and now it's probably a lot easier. Maybe I should look into how much work this would be again ... [1]: https://sorbet.run/talks/StrangeLoop2018/#/28 https://sorbet.run/talks/StrangeLoop2018/#/28
- davidatbu 5y agoI tried using sorbet through it's LSP in neovim. I would get diagnostics not after every change, but after every change that adds/removes a function definition. Effectively, I would have to delete and add back a function definition, or the "end" keyword, to trigger sorbet to type check my code. Is that supposed to be how it works?
- e12e 5y agoThank you for writing up such a nice overview. I've been busy trying to get tests into various legacy projects I've inherited - definitely interested in tackling types next. Couple of questions: > The declare_method call above acted like a decorator on the def call method: it would check that the msg argument given to call was a String and that call returned a String on every invocation How was this implemented? Dynamically altering Object#send or something along those lines? How is the story for sorbet and vim/nvim? Are there "run time" or "code gen" uses for sorbet? Like generating swagger/openapi documentation/schemas based on typed Api methods? Or vice-versa - scaffolding sorbet-typed Api from a swagger.json? Or something similar for graphql (or, well, SOAP..)?
- jez 5y ago> How was this implemented? The mechanism in the post is the same mechanism in use today with Sorbet signatures. To get the `sig` method in scope, you have to put `extend T::Sig` in that class (or one of its parents). When `sig` is called for the first time in a class, it monkey patches that class to install some overrides of the method_added method. Ruby calls this method_added method for you every time it creates a method (from any means, static or dynamic). Code is here: https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/_methods.rb#L530 https://github.com/sorbet/sorbet/blob/master/gems/sorbet-run... > How is the story for sorbet and vim/nvim? Great, honestly better than VS Code for everything except autocompletion. I personally use Sorbet with Neovim. > Are there "run time" or "code gen" uses for sorbet? Like generating swagger/openapi documentation/schemas based on typed Api methods? Or vice-versa - scaffolding sorbet-typed Api from a swagger.json? Or something similar for graphql (or, well, SOAP..)? Not that I know of unfortunately, but I also pay more attention to the type checker and it’s bugs than the tooling people build around it to get real world work done
- hiphipjorge 5y agoWe've been starting to use Sorbet at Figma and honestly it's been pretty cool! Sorbet is definitely not as good at TypeScript (yet?). It's more verbose, doesn't support things like recursive types and records (shapes are experimental), and it doesn't inspire the same confidence TS does but it's definitely worth it to add it to your codebase if it's big enough! Also, it's fast! I'm in total agreement with the point made in the article. That makes a huge difference in developer UX.
- jez 5y agoI have some concrete ideas for how to fix shape types to make them not incremental. Just a matter of finding the time to push the prototype over the line, and do a migration on Stripe's codebase to fix or silence the ensuing errors. It's one of the most requested features for sure, and I think once we implement it Sorbet will feel much better to use, especially in smaller projects and scripts where you don't want to have to define `T::Struct` for one-off data structures.
- jez 5y agotypo past the edit deadline: "not experimental" instead of "not incremental" (I've got incrementality on the mind these days it seems)
- hiphipjorge 5y agoWow that would be awesome! Shapes/Records are definitely the #1 thing I'm missing from moving over from TypeScript. I started thinking about this a bit and I came up with the conclusion that the single biggest difference between structs and shapes is really iterating over keys. I spent some time trying to create structs by which you could iterate over all the keys and all the solutions seemed clunky or inelegant.
- weaksauce 5y agohuh... didn't expect figma to be using any ruby. what do you all use it for there? I'm mainly a ruby programmer lately but I used figma for my last project design and it was really lovely to use so good work!
- tootie 5y agoTypes for Ruby, types for JavaScript, types for Python. Why didn't we all just stick with Java?
- ecshafer 5y agoI've worked with Ruby + Sorbet, and also with Java. I would rather write Ruby + Sorbet than Java right now. Ruby is a really nice language. Though Java still has some great strengths, especially the 8+ functional programming features and the concurrency library is great. If I could use Rails with Java it might be a different story though, since I hate Spring.
- agotterer 5y agoYou could use JRuby
- zingar 5y agoJRuby is a really cool project! Unfortunately it's fairly far behind mainstream Ruby. They are still working on Ruby 2.7 compatibility, but Ruby 2.6 end-of-life is 2022/03/31 (tomorrow).
- zingar 5y agoI started my career in Java and although I loved IntelliJ and was perplexed by how poor RubyMine seemed by comparison (circa 2012) that's not enough for me to go back from Ruby to Java. What I'm realising now is that Java has (had?) a really clunky type system and that there are other languages that do types better, so I shouldn't use Java as my reason for avoiding them.
- tootie 5y agoIf I put together my top ten language features it would include documentation (both for the core language but also the doc framework for coders), legible stack traces, tight IDE integration (this typically requires strong+strict typing), easy debugging, first class build tools for perfectly reproducible builds and dependency management, and easy version management. Java ticks all those boxes. In fact, I think the thing that launched Java in the first place was not nonsense like checked exceptions, it was javadoc. In terms of syntax and semantics, I've never felt any more or less comfortable coding in anything in the Algol family. Anyone who's been coding long enough knows that 98% of the complexity in building software is people. And anything that helps you understand other people's code is 10X more valuable than anything that helps you code faster.
- reichertjalex 5y agoI remember having mixed feelings about Sorbet when I first joined Stripe in late 2018, but by the time I left, I found it indispensable. Especially after the VS Code extension was released internally... holy crap, that made such a huge difference (vs having CI fail 20 mins after pushing up a PR because you forgot to run the typechecker script ahead of time, ugh). This article also made me laugh, because it reminded me of one of my small pet peeves about the Ruby codebase at Stripe: the fact that you would often find `merchant`, `account`, `invoice`, etc used as method parameters that represented the _ID_ of the resource rather than the resource itself. So Sorbet definitely helped with that, but it also could've been nice to just write `invoice_id` instead... :P Makes me nostalgic though, good times!
- clintonb 5y agoI also joined Stripe in 2018, and thought Sorbet was a waste of time. I quickly changed my mind when I realized how many incidents it prevented. Now I want types for :allthethings!
- itslennysfault 5y agoThis is exactly how I felt when I was first forced to use TypeScript instead of JavaScript, but I can't even tell you the number of hours it has saved me. Now, years later, I can't stand using regular JavaScript, and would never recommend it for any project that will be beyond a toy.
- Existenceblinks 5y agoI use Elm but not Typescript. I also write plain javascript intensively. I've seen languages adding static type layer after the fact they are dynamic type. They never end well, lots of edge case here and there, it becomes a problem rather than helper. Static type lang needs to think from start how they will compose nicely, what's pure, what's effect etc. However, the ship has sailed, I don't actually want to debate on this, because web tech is becoming a cult. (the last phrase added for downvote bait, lets do that!)
- 5y ago
- ffggvv 5y agokinda funny how stripe is hyped so much yet they still use ruby...
- henning 5y ago[flagged]
- Fire-Dragon-DoL 5y agoI tried using sorbet on our project, but the type system it supports is way too poor. The most glaring problem is the lack of support for duck typing, only nominal typing is supported, no structural typing. For key parts of the code, there was no type safety where we expected it. In the end, it felt far from being like Typescript, we opted for removing it, instead we added some runtime type checking and we document with YARD. Far from ideal, but that's the tooling available. The gem integration is terrible currently: we wrote the gem, fully typed with sorbet, but for some reason the type checking was completely ignored in the main project where we referred it
- brigandish 5y agoMy problem with Sorbet is how ugly it looks. It's probably not possible to do with Ruby (now) but Crystal does it better[1] while (mostly) retaining the syntax of Ruby. I suppose it's trying for Elixir's @spec, which I like less than Crystal's sigs but still prefer over this. Still, if it helps it helps. [1] https://crystal-lang.org/reference/1.3/tutorials/basics/60_methods.html https://crystal-lang.org/reference/1.3/tutorials/basics/60_m... Edit: Perhaps I spoke too soon https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in-action.html https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typ...
- faitswulff 5y agoI haven't used it, but I actually appreciate how Sorbet is written in Ruby syntax. After a bit of adjustment, it's pretty easy to grok.
- throwawaypls 5y agoSorbet is very useful, but the ergonomics suck. It’s fucking difficult to write rspec tests. The performance overhead of writing sorbet on rails in a big codebase is so much that we have turned it off. The pre-interpreter type checking is somewhat useful. The alpha releases are also a big concern. We are stuck on a 300 commit (release) old build and can never upgrade safely. We have also never been able to get the VSCode extensions to run. Thanks for Sorbet, but I’d suggest people outside Stripe to look elsewhere.
- mdoms 5y agoTo be honest I struggle with the frustration of programmers of dynamic languages building their own (mostly half-assed) type checkers, after years and years of dealing with their bogus arguments about why dynamic languages are superior and all you need is unit tests (the same cohort is also now on a misguided crusade against unit tests but that's a whole other story). At least the JS crowd had the decency to buy into a whole new (far better) language instead of a bolt-on solution.
- gouda-gouda 5y agoSorbet is sooo close to being idiomatic in Ruby. With some support for duck typing and roughing out edges around memoization / ivars, I could see it being adopted into Ruby proper.
- davidatbu 5y agoI spent about two weeks trying to use Sorbet while working on a rails codebase. For context, I'm a huge typing fan, and use it even when writing 10 line scripts in Python. Here were my feelings: The TLDR for me is: I’d still be willing to keep using sorbet, if issue number 4 below (that the LSP isn’t very responsive) would be fixed. Otherwise, it adds more work than it removes from my workflow, so I've stopped trying it out. Start with the negatives: 1. It’s a lot of grunt work to set it up properly in an application like ours with many dependencies. Specifically, sorbet and/or related tooling tries to generate RBI (equivalent of typescript’s index.d.ts) files by actually importing and running your code, and doing introspection on the types of the arguments of functions. I managed to work around this problem, but it’s a reflection of how young Ruby typing is as a whole that this step was a time-sink for me. 2. There’s no mechanism to do the equivalent of yarn add -D @types/react-table (which would install typings for the react-table package). You basically have to copy paste from this github repo[0] manually. 3. Some really popular gems still don’t have types. For example, IIRC, devise’s typings are either non-existent or are uselessly incomplete. 4. The sorbet LSP isn’t very responsive, at least in neovim. I asked @jez about this just now, so hopefully I'll get a response. 5. Super verbose syntax. Positives: 1. Thanks to this repo[1], there’s actually a way to easily generate typings that would cover a lot of the dynamism of rails, it works quite well. 2. It’s legit helped me catch errors with my code. [0] https://github.com/sorbet/sorbet-typed https://github.com/sorbet/sorbet-typed [1] https://github.com/chanzuckerberg/sorbet-rails https://github.com/chanzuckerberg/sorbet-rails
- zingar 5y agoSorbet and/or RBS seems like they may be the future given how popular typescript is with JS programmers these days. There are some other projects that assist programmers without relying on formal type definitions in the source or shadow typing files: Solargraph combines inference and insight from YARD docs (standard for many gems, plus Castwide has written more YARD for the standard library) to make some pretty good guesses. Crucially it has plugins that add the insights from popular gems with static analysis (e.g. reek, rubocop). I maintain solargraph-rails, which parses your Ruby to make guesses about (surprise) Rails. The typeprof gem can help IDE plugins make typing guesses based on your tests. This project is interesting to me because it's going into Ruby 3.1 so I think it reflects awareness from the core ruby team that many programmers are not ready to add types to their code. solargraph: https://github.com/castwide/solargraph https://github.com/castwide/solargraph solargraph-rails: https://github.com/iftheshoefritz/solargraph-rails https://github.com/iftheshoefritz/solargraph-rails typeprof: https://www.youtube.com/watch?v=UTMj51j9yEg https://www.youtube.com/watch?v=UTMj51j9yEg
- throwaway923032 5y ago
- iostream24 5y agoI guess they never heard of crystal lang