I haven't used Ruby in ages but this seems like a really odd way to incorporate type hints in the language.
I much prefer the Python 3+ approach of type annotations in source code.
I can't imagine having to look at a separate file just to figure out what the type of something is. You may say "tooling will fix this" but it's just far less overhead for everyone at the end of the day to just make annotations in source.
My more existential question is, is there really an advantage to doing static type checking in Ruby?
When I was doing Ruby, the way you can change objects on the fly, add methods on the fly, the vast amounts of metaprogramming, are types at "compile" (I know, not really) time really the same as types at runtime?
Like, it might be nice to get some autocomplete, but AFAIK tools already do that (RubyMine, others).
> I can't imagine having to look at a separate file just to figure out what the type of something is. You may say "tooling will fix this" but it's just far less overhead for everyone at the end of the day to just make annotations in source.
TypeScript has this functionality (in addition to being able to write actually TypeScript files with inline annotations. The big advantage is being able to provide 3rd party type definitions for libraries that don't provide them and aren't interested in using them. This allowed TypeScript to bootstrap decent library support well before it was popular enough that the mainstream was considering adopting it, and this in turn enabled widespread adoption.
> My more existential question is, is there really an advantage to doing static type checking in Ruby? When I was doing Ruby, the way you can change objects on the fly, add methods on the fly, the vast amounts of metaprogramming, are types at "compile" (I know, not really) time really the same as types at runtime?
Again, I think TypeScript shows that there is. Sure, there are times when you want to do super-dyanamic stuff. And you can opt out of type checking using the "any" type in those cases. But a lot of the time you're not doing anything complicated, and you just want a compile-type check that ensures you're passing the correct type to the function you're calling.
There have been attempts to have types outside the main source or in comments for many dynamically typed languages. They seem to fail due bad programming ergonomics, as maintaining separate "header" files is cumbersome (hello C my old friend).
This is why I do not like mypy or types in Python other than dataclasses. If I'm going to type the damn thing I better be getting performance ala cython. Why on earth use a dynamic language like Ruby or Python and then try to bolt types on top. Ruby would do far better to fix the bloody `and` vs `&&` issue (it should just be `and` and it should work like `&&`) and strings should be immutable by default with a special syntax or method to make them immutable.
But you're absolutely right about the downsides of stuffing types into a different file. I get why Matz did it (he wants to keep Ruby beautiful and types are crufty) but I don't like them in the first place.
> Why on earth use a dynamic language like Ruby or Python and then try to bolt types on top.
To answer this (as someone who basically only ever writes in Python):
There are a few cases where it's really nice to be able to add type annotations to methods or functions. The most obvious example is API calls; it's nice to be able to say "this needs to be a list, give me a list", and not have to do
if not isinstance(var, list):
var = list(var)
or
if not isinstance(var, list):
raise ValueError("I know I didn't tell you I needed specifically a list, but I need specifically a list in this case")
Over and over and over again all over your module. Look, give me a list, I need a list. I need the APIs that list has, I need the interface it uses. I don't want a generator that I'm going to be iterating over forever, I don't want a string that's going to get split into individual characters.
Duck typing is all well and good, but just because strings, lists, sets, and os.walk are iterable doesn't mean I'm able or willing to handle those.
It can also help a lot in IDEs; for example, if I type-annotate a method to accept "name" as a Str, then my editor can assume that "name" is a string, even without any other evidence to that being the case. Likewise for things like warning about return types.
Lastly, it lets you do automated testing as well. Hey, you're passing a FooNode to this function, but that function accepts a list. I know this because NodeCollection.find() returns a FooNode. Makes it easy for the dev to look at the report and think "Oh, I meant to use NodeCollection.findall(), oops!"
I certainly don't want a statically typed language, but there are a lot of cases where my internal logic is fixed and I don't want my method to have to know how to deal with int, str, none, bytes, etc. Type annotations can solve this problem for me and for other people using my code.
I just worry it's going to be abused, though. E.g. I've worked with more than one Ruby code base where someone did a kind_of? check and threw exceptions if it didn't get what it wanted even though the actual type required was anything that implemented a given method in a reasonable way for no good reason.
I hope people keep the type annotations sparse, and allow the tools to infer it unless they're prepared to link long and hard about the minimal restrictions that are reasonable.
I think your own example proves that your concern is moot. If people are going to do stupid stuff, they're going to do it with whatever tools are available to them. Your kind_of misbehavior already happens without type annotations, but now it can be clear to you beforehand what's going to happen.
I hope you're right. I just fear that making it external to the code will make it easier for people to ship overly restrictive type signatures without thinking. Though hopefully the tools like sorbet will make it easy to override, in which case it might well improve things (if I "only" need to override the type signatures instead of having to monkey patch or fork code)
> Why on earth use a dynamic language like Ruby or Python and then try to bolt types on top
Probably using the languages for the ecosystem (e.g. Python for scientific computing or ML and ruby for ruby on rails) but still wanting to benefit from type checking
I think it's moderately clear that they're not intending this be a complete solution to type checking in Ruby, but rather a starting point that the community can build on top of.
For instance, I can imagine adding something like comment blocks to Ruby code that RBS tooling can find and treat like the RBS files.
Treating it as a starting point is, I think, the best justification for putting them in separate files. I don't quite like the idea of having them in separate files, but at least that way replacing it or evolving it without breaking peoples code-bases will be easier.
Adding type checking sort of clips the wings of a codebase, but makes it far less magic, and when you're a company the size of Square or Stripe you want as little magic as possible.
I much prefer the Python 3+ approach of type annotations in source code.
I can't imagine having to look at a separate file just to figure out what the type of something is. You may say "tooling will fix this" but it's just far less overhead for everyone at the end of the day to just make annotations in source.
My more existential question is, is there really an advantage to doing static type checking in Ruby?
When I was doing Ruby, the way you can change objects on the fly, add methods on the fly, the vast amounts of metaprogramming, are types at "compile" (I know, not really) time really the same as types at runtime?
Like, it might be nice to get some autocomplete, but AFAIK tools already do that (RubyMine, others).