18 ms·
Open-sourcing Sorbet: a fast, powerful type checker for Ruby
- simplify 7y agoA fascinating part about Sorbet is it did not have to introduce any additional syntax to Ruby (unlike TypeScript, for example). This really speaks to the expressiveness of Ruby. Very cool.
- est31 7y agoWas that additional syntax in TypeScript actually neccessary for type inferrence? Or is it rather to avoid API hazards when you change some internal code and suddenly the API of your library breaks because the inferred type has changed.
- idle_zealot 7y agoYou can use TypeScript in a mode that only uses type inference and doesn't require type annotations or definitions. It works surprisingly well.
- matt_kantor 7y agoAdditionally, TypeScript will parse JSDoc comments into type annotations: https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#jsdoc-types-are-used-for-type-information https://www.typescriptlang.org/docs/handbook/type-checking-j...
- loxs 7y agoIt is necessary because of some limitations, but IMO it's also a great idea nonetheless. Types' names are a great documentation, one which you can't get with pure inference. Even languages that have (close to) the best possible inference, like OCaml, still have additional syntax for defining types, because it 1. gives you documentation and 2. allows you to do things that are mathematically proven to be impossible via inference. TS is great, and also moving really fast and becoming better every 2-3 months.
- SomeOldThrow 7y agoThe type signatures are pretty noisy to read, though, some syntax can definitely help. Maybe with Ruby 3?
- hderms 7y agoI'm interested in whether `.rbi` files are going to be the only official route for typing in Ruby, and if so, how that would end up impacting Sorbet?
- ctrlaltdylan 7y ago!remindme in 1 month to check out Sorbet case study posts
- deleted 7y ago[deleted]
- imhoguy 7y agoExcellent work! I wonder if somebody already tried to run it against Rails codebase.
- darkdimius 7y agoYes, join the slack! While Stripe doesn't run rails all other companies in private beta did!
- ksec 7y agoBoth Shopify and Kickstarter are on Rails. I wonder why Github didn't join the private Beta?
- heartbreak 7y agoMaybe their forked version of Ruby had something to do with it.
- WA9ACE 7y agoIIRC last time I poked at their enterprise image and dumped the code there were bits of custom type annotations. That was around 2-3 years ago.
- ptarjan 7y agoThe parser for Sorbet was actually entirely given to us by GitHub. They were fabulous partners early on in the project and we're grateful for their contributions.
- heartbreak 7y agoUnderstated, but awesome! Thanks for the insight!
- deleted 7y ago[deleted]
- 7y ago
- itake 7y agoThe "Getting Started" link at the bottom of the page is broke https://sorbet.org/blog/2019/06/20/docs/adopting https://sorbet.org/blog/2019/06/20/docs/adopting
- ptarjan 7y agoGreat find! Fixing now, thanks.
- anonova 7y ago> To enable static checking with srb, add this line (called a sigil) to the top of your Ruby file: > # typed: true Isn't this called a directive/pragma? A sigil is a symbol on a name. Either way, I'm excited to see this finally out after seeing the past presentations on it.
- ptarjan 7y agoThanks for pointing that out. It can be called all of those. We liked sigil from its connotation: > Google defines sigil as, “an inscribed or painted symbol considered to have magical power,” and we like to think of types as pretty magical https://sorbet.org/docs/static#fn1 https://sorbet.org/docs/static#fn1
- dochtman 7y agoThat's a pretty unintuitive use, since "sigil" is more commonly used (in programming languages) as a single symbol, as in a non-alphanumeric character that's used as some kind of syntax. https://en.wikipedia.org/wiki/Sigil_(computer_programming) https://en.wikipedia.org/wiki/Sigil_(computer_programming)
- sdegutis 7y agoI remember there was this CEO once who was new to the software industry and was looking for a word to describe non-small-business customers. When people suggested "Enterprise", he instantly dismissed it, and when they insisted this is already the word we all use to mean this, he actually opened the dictionary to prove that it wasn't quite accurate. What I took from this is that, when cultures or conventions already have momentum, sometimes you just have to go with it. This is the same reason I don't like Go.
- hartator 7y agoAwesome work. sig {params(person: Person).returns(Integer)} def name_length(person) Not sure if I dig the syntax. Furthermore arguments seems to be the official names for method arguments, not parameters. eg, `ArgumentError`. `params` also feels like it's linked to Rails `params` variable in controllers. It can be confusing. Something like this will also feel more Rubyist: def name_length person: Person, return: Integer But it probably requires a deeper hack or a change in MRI.
- ptarjan 7y agoThanks for the idea. We used `params` because Method#parameters was what they called it in the standard library. I actually had it as `args` originally until someone pointed this out. https://ruby-doc.org/core-2.6.3/Method.html#method-i-parameters https://ruby-doc.org/core-2.6.3/Method.html#method-i-paramet... As for the syntax change, we are actually on our 8th iteration of the syntax. We really wanted this to NOT be a fork of Ruby so finding something compatible was very important. For example that's why it has the weird `sig {` syntax too, we didn't want to have to cause load-time and cyclic dependencies from adding type signatures.
- hartator 7y ago> We used `params` because Method#parameters was what they called it in the standard library Super interesting. We should probably have being consistent for naming parameters vs. arguments in stdlib. It's too late though!
- steveklabnik 7y agoOn a super pedantic level, "parameters" are the names that you write in the function definition, and "arguments" are the values you pass as parameters. def name_length(person) steve = Person.new name_length(steve) Here, 'person' is a parameter, and 'steve' is an argument. Most programmers use them interchangeably.
- learc83 7y ago
- mark_l_watson 7y agoThanks for this. Major contribution to the Ruby community!!
- ptarjan 7y agoThank you! Ruby has been kind to us, we'd like to be kind back.
- the_duke 7y agoIt's been funny to watch how more and more static type systems are getting bolted on to dynamically typed languages in recent years. Typescript (with stellar adoption), native type annotation support in Python, Sorbet, PHP 7, Elixir + Dialyzer, ... I wonder why there isn't a popular gradually typed language that natively allows writing both dynamic and type-safe code, allowing quick prototyping and then gradual refactor to type safety. I guess in part because it's a big challenge to come up with a coherent type system that allows this, the bifurcation in the ecosystem, and often a somewhat leaky abstraction. Eg in Typescript you will often still run into bugs caused by malformed JSON that doesn't fit the type declaration, badly or insufficiently typed third party libraries, .... Google's Dart is the only recent, somewhat popular language (only due to Flutter) that allows this natively - that I can think of right now. I do think such a language would be very beneficial for the current landscape though, and projects like this show there is a clear need. Edit: just remembered another option: Crystal. Also Julia, as pointed out below.
- jez 7y agoHave you seen Julia? https://julialang.org/ https://julialang.org/ The first three selling points on their home page are Fast, Dynamic, Optionally Typed.
- deleted 7y ago[deleted]
- Buttons840 7y agoMeh. Julia does nothing to help me catch errors before runtime, it's no different than Python in this regard. Although it does use the types to generate fast code (and in my experience it does live up to its performance claims). I've seen some talk of Julia doing compile time checks, maybe in the future it will?
- ddragon 7y agoJulia's type system works to make the language more dynamic instead of more static, instead of one slow program in which types have to be checked at runtime a Julia program is a superposition of every optimized implementation of an algorithm and explicit types are used to access and manipulate a subgroup of implementations. So in general you don't use types to increase performance (the compiler already infers all types to generate fast code, including optimized union types for nullable fields for example), but when you want to restrict the polymorphism to work in more specific code. But since the JIT compiler infers all types, regardless of explicit hinting, it's possible to create tools to evaluate all types before a function is called [1], which combined with the efforts in better precompiling the code (which increases the amount of code that will have the type inferred before running) could make some good tooling for compile time checking. [1] https://nextjournal.com/jbieler/adding-static-type-checking-to-julia-in-100-lines-of-code https://nextjournal.com/jbieler/adding-static-type-checking-...
- jedisct1 7y agoVery cool stuff!
- maxfurman 7y agoI'm having trouble finding the implementation of `sig` - could someone please point me to the right file? Thanks. I'm very curious how they pulled this off.
- jade12 7y agoHere it is: https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/sig.rb https://github.com/sorbet/sorbet/blob/master/gems/sorbet-run... As you'll notice, `sig` doesn't actually do anything.
- maxfurman 7y agoSo....then....how does Sorbet use the type signature provided to sig?
- andolanra 7y agoSorbet has two components, broadly speaking: the typechecker, which is a standalone application, and the runtime, which is a Ruby gem. The typechecker can parse Ruby code and find the sig blocks in the code to extract type information from them, and it then can use this type information to perform type-checking without ever loading a Ruby interpreter, to say nothing of the actual Ruby code in question. This is intended to happen in a CI pass or a pre-processing step, but it is entirely offline. On the other hand, when you run your code, the sig blocks may or may not be used. There's a lot of machinery in https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/_methods.rb https://github.com/sorbet/sorbet/blob/master/gems/sorbet-run... that handles understanding what a sig means and installing a wrapped version of a method that does type-checking on entry and exit. The intention is that the standalone sorbet executable should be used during development, but it can't catch every error, so the runtime system will double-check types at runtime, which is especially helpful when some parts of your code are typed and other parts are untyped, and control flow passes back and forth between those sections: the runtime will ensure that you don't accidentally pass an object with an unintended runtime type into code with static type expectations.
- FpUser 7y agoI've never understood the so called advantages of dynamic typing. To me it looks like a land mine in one's project waiting to blow at run time. And what for? Do developers code so fast that the time spent on typing something like "int i" will provide any real savings? Now vendors are trying to patch those with bolted on top syntax extensions/derived languages that need to be transpiled. What a mess.
- camjohnson26 7y agoSometimes I do code that fast. When you’re messing around just trying to find out if something is possible you want to write as much code in as short a time as possible, and Python shines for that. Every second wasted typing is a second that could have been spent moving forward. Of course the problem is that the prototypes are terrible to maintain and eventually need unit tests and typing. But you don’t want to waste time adding those things if you’re not even sure your idea will work. I use strongly typed languages in production and couldn’t imagine using Python for that.
- FpUser 7y agoThis logic transpiles(TM) to this in my brain: a) I am messing around and need to write 10 lines of code. Can I write it fast and without thinking? Sure and not needing to type int/float/whatever will save me couple of seconds. If I iterate and rewrite that short piece 10 times then I just saved 20 seconds. Chirp chirp ... . Do I need special language for just that? Lemme guess ... b) I am messing around and need to write few hundred or thousands lines of code. Can I write it fast? Maybe but it is likely that good chunk of time will be spent thinking. I doubt that at this point not declaring type will save anything worth noticing. But that of course is my opinion
- camjohnson26 7y agoTyping speed is only one constraint that type systems enforce. They also make it much slower to change input or output data from functions. I tend to use dynamic languages for projects between 100-500 loc and usually I’m not really sure what the final design will look like so need to try a lot of different ideas as fast as possible.
- jtms 7y agoThough I haven’t yet used it for anything in production, I think if I were starting something greenfield and wanted “Ruby with static types” I would go with Crystal. I really enjoy writing it and the performance you can get is quite a significant boost over Ruby.
- sickcodebruh 7y agoI’d still go Ruby. A language’s ecosystem and community are as much factors in why someone should choose or avoid it as its syntax. Both of those things are fantastic for Ruby — I’d argue that they’re some of its best features, in fact. Crystal? Not so much.
- jtms 7y agoI’m a long time veteran of Ruby and someone who deployed production Rails apps in EARLY v1. I absolutely love and adore it and it’s by far my favorite language to work with. That being said, when I can write in a very stunningly similar language and get 10 to 100x performance with very little extra effort I am going to strongly consider it when deciding on my stack. Also the ecosystem for crystal is not terrible at all. I think it’s a great project and shouldn’t be ignored because “the ecosystem”
- sickcodebruh 7y agoI also think Crystal is a great project that shouldn’t be ignored because of its smaller ecosystem. The performance boost is nothing to sneeze at and I wasn’t suggesting that Crystal’s ecosystem is terrible, but it’s nowhere near Ruby’s. EVERY problem has been solved in Ruby. (Edit: Every problem that isn’t hamstrung by Ruby’s technical limitations. It’s certainly not the right tool for every job.) There’s a gem or a blog post or a service for everything and it will probably work very well. The language is stable and predictable. It won’t be as fast as we might want it to be but it’ll probably work with minimal effort out of the box. You’ll be able to put it into production with little to no fuss and it’ll just work. If it doesn’t just work, you’ll have no problem finding resources to get it resolved. I don’t think you can say any of that about Crystal. Maybe that stuff doesn’t matter for every individual or every project but for me, they’re significant enough that I think they should come up whenever anyone tries to compare the two.
- mbell 7y agoI wonder what the reason for not supporting structural typing is. It seems like a very natural fit for Ruby.
- ptarjan 7y agoWe believe that the main goal of a typechecker is to give you nice error messages. We've found giving names to things makes it easier for folks to reason about their errors, and introducing names for interfaces isn't that onerous at Stripe or with our beta testers. We aren't opposed to eventually support it, but we'd like to see how it goes with the current form first.
- castwide 7y agoCoincidentally, I announced the beta version of a Ruby type checker in Solargraph two days ago: https://github.com/castwide/solargraph/issues/192 https://github.com/castwide/solargraph/issues/192 It has a few overlapping features with Sorbet, with one major difference being that Solargraph type checking relies on YARD documentation instead of annotations.
- brigandish 7y agoI wondered why no one had tried this instead. It's certainly a (much needed!) incentive to document methods. I'm going to give Solargraph a look-see.
- hit8run 7y agoLove the work you’re doing on Solargraph! Thx for it.
- poorman 7y agoI've used contracts any time this type of thing was necessary. https://github.com/egonSchiele/contracts.ruby https://github.com/egonSchiele/contracts.ruby
- wpride 7y agoWe use Contracts too and are in the process of transitioning to Sorbet. In addition to the same runtime type checking as Contracts, Sorbet offers static type checking (and will re-use your runtime signatures in its static analysis).
- poorman 7y agoThe dependency on bazel is very off-putting to me. After having tried it for other projects and watched as rapid breaking backwards incompatible changes were made to the tool, I'm opting out of anything requiring it.
- darkdimius 7y agoWe only use it at build time, as for a user, it should be invisible for you
- bakery2k 7y agoDoes anyone know what Matz thinks of Sorbet? He has previously been opposed to adding type annotations to Ruby [1]. This is in sharp contrast to Python, where Guido has overwhelmingly embraced type annotations. [1] https://bugs.ruby-lang.org/issues/9999#note-13 https://bugs.ruby-lang.org/issues/9999#note-13
- baroffoos 7y agoI saw some news saying that ruby 3 would have types built in.
- whycombagator 7y agoRelated, Square wrote a great article: "RubyKaigi and the Path to Ruby 3"[0]. The section titled "Static Analysis" high level compares Sorbet to Steep [0] https://developer.squareup.com/blog/rubykaigi-and-the-path-to-ruby-3/ https://developer.squareup.com/blog/rubykaigi-and-the-path-t...
- sickcodebruh 7y agoIt’s my understanding that the Sorbet team is involved with bringing types to Ruby 3. I’m unclear on whether it will be Sorbet itself or if it’s elements of it. Can’t dig up the source right now, maybe someone can corroborate this?
- darkdimius 7y agoThis is true, we're part of a single working group that's working on types for Ruby3. https://twitter.com/darkdimius/status/1119130004313350144 https://twitter.com/darkdimius/status/1119130004313350144 has some detail
- battletested 7y agoI really don't like the current movement of introducing static typing in dynamic typed languages. Why did we create dynamic typed languages after all? Because we know the pain of having to type everything and especially the pain of converting one type into another. In C you mainly need static types because you cannot put 32 bits into a 16 bit CPU register, or you cannot do pointer arithmetic on values of different types, etc.. But that is not the reason why we want types in dynamically typed languages. We just want to prevent passing incompatible types as argument to a receiving method for example. And by adding static typing to dynamically typed languages we actually invalidate these languages entirely, full circle. First we create a dynamically typed language, then we change that into a statically typed language that transpiles back to a dynamically typed language, which is terribly inefficient, why would you want that? I have never been able to convince the proponents, they appear to be in some kind of higher state, having found the holy grail. Almost all the benefits of static typing added to dynamic languages can be achieved by a better and smarter IDE. All these new 'typed' languages with all their own issues are only temporal I expect. We keep changing and rewriting while thinking we're doing it 'the right way' now.. Like Typescript, how long will that live? Flow is deprecated already. The very best thing of the C language is that it is still C, and that is fucking awesome for C developers, after all those years they can still write in the language they master.
- jldugger 7y ago> Almost all the benefits of static typing added to dynamic languages can be achieved by a better and smarter IDE. That does what? Typecheck things? You'd probably want a tool or library to do that, so it's pretty fortunate that Stripe bothered to provide those to us. > We keep changing and rewriting while thinking we're doing it 'the right way' now.. Humanity has yet to rescue type inference from the forges fire on Mount Olympus. After Milner tricked Hindley in a lunchtime debate about tabs versus spaces and code quality, Hindley punished us mere mortals by placing type inference in the fires of heavily recursive forges mortals dare not touch.
- sebastianconcpt 7y agoI still wonder what problem exactly static typing fixes. Is is only me that I'm too used to dynamic tech without correctness issues?
- SmooL 7y agoWell one thing: when you work on enough code for long enough you start to forget what's/what. Static typing let's you jump in and immediately know the shape of your data at any point in the code, without having to re-trace execution manually
- danenania 7y agoYep, many projects grow to a point where it’s literally impossible for anyone to keep track of the full data model in their head. At that point, you end up needing to refer back to schemas and/or jump around the codebase constantly in order to to get anything done. This is when static types and IDE autocomplete start saving you significant time and energy.
- sebastianconcpt 7y agoI know what you mean. I code in ways that compensate for that problem. Actually minimizing my need to remember anything but intentions. Smalltalk rewards that style vehemently for example. And I use Smalltalk and JavaScript in that style and I don't feel any less productive or particularly vulnerable to correctness issues. While in a TypeScript project I'm working I do feel a productivity hit (not in the good sense) and I (and my teammates) keep asking ourselves "what TS is helping us with, really"? So far the only thing I can say is that TypeScript is a fantastic solution for all the problems that it creates itself.
- SmooL 7y agoI'm genuinely curious how you code in such a way that compensates for the problem. I'm unfamiliar with Smalltalk but very familiar with JS/Typescript. I'm sure that if tight-knit group held strongly to certain naming conventions, then you could convey structure through the semantics. My experience though has been that anything that relies heavily on "holding strongly to convention and form" falls quickly to business speed, forgetfulness, and laziness. As a personal example, a couple of coworkers and I were working on a codebase in JS, and there was some function that helped manipulate user objects. There were however two types of user objects, that were _very_ structurally similar, but with slight variances. Because of the similarities, people started calling user functions with one type that were meant for the other type, and it would work. Future modifications would inevitably cause failures, as unexpected parameters were being passed in.
- dajonker 7y agoI have mixed feelings about adding type annotations to an existing project. IDEs become easier to use, you can avoid certain bugs, refactoring becomes a bit less error-prone. But this comes at a cost: you need a very high type coverage, which means you need to rewrite a lot of code to deal with the different style of polymorphism. It's very likely that you end up with code that looks as if it were written in a statically typed language but without any of the performance benefits of such a language.