
RBS, Ruby’s new type signature language - mootrichard
https://developer.squareup.com/blog/the-state-of-ruby-3-typing
======
avolcano
Didn't realize Square was interested in Ruby type checking, just like their
competitors over at Stripe. Lots of money riding on Ruby, I guess :)

It does seem useful to have a _standard_ for type definitions - RBS as the
equivalent to a .d.ts file - as that allows for different type checking
implementations to use the same system under the hood. This was a big problem
for Flow, and why it lost the fight as soon as TypeScript's definitely-typed
repository started gaining momentum - users wanted to use the type-checker
that they knew had definitions for the libraries they used.

On the other hand, RBS as hand-written seems rather dangerous, to me. Nothing
wrong with using them to define previously-untyped external code, as long as
you know the caveats, but I think you really want to have definitions
generated from your code. Sorbet cleverly (and unsurprisingly, given it's
Ruby) used a DSL for definitions in code, which had the (excellent) additional
boost of runtime checking, so you actually could know whether your types were
accurate - by far the biggest pain-point of erased-type systems like
TypeScript.

Given that Ruby 3 was supposed to "support type checking," I'm surprised that
it does not seem to have syntax for type definitions in code, and instead will
focus on external type checking. I might be missing a piece of the full puzzle
not covered in the blog post, however.

~~~
ric2b
> I'm surprised that it does not seem to have syntax for type definitions in
> code

This is a big disappointment to me, one of the main advantages of static
typing is that it can make code much easier to understand when types are added
to non-obvious method parameters.

~~~
cutler
Surely a good IDE such as RubyMine would be able to display the type in
reponse to, say, a mouseover?

~~~
derwiki
RubyMine tries but it certainly works less well than PyCharm and GoLand.

~~~
d3nj4l
RubyMine works great if you add yard type docs to your code.

~~~
Lio
Is it checking those types or just reporting then?

I’ve seen so many instances where yard doc has the wrong return type or misses
a return type that I rarely trust it.

~~~
d3nj4l
It doesn't do typechecks, no. You can use solargraph
([https://github.com/castwide/solargraph](https://github.com/castwide/solargraph))
for that.

------
setpatchaddress
I'm really puzzled by the decision to use a separate file for this. The stated
justification ("it doesn't require changing Ruby code") doesn't make sense,
and my personal experience with languages with external type specifications is
strongly negative. It's an unbelievable pain to keep multiple interface files
in sync over time.

`.h` files are not something to emulate! External interfaces should be
generated by tools where needed.

~~~
rattray
FWIW, you can use inline syntax with Sorbet[0], one of the two typecheckers
that will work with the RBS format (the other being Steep, which does not have
inline syntax).

Here's a full example, complete with a typo, based on the example in the blog
post: [https://bit.ly/3hMEMSp](https://bit.ly/3hMEMSp)

Here's a truncated excerpt to get the basic idea across:

    
    
        # typed: true
    
        class Merchant
          extend T::Sig
    
          sig {returns(String)}
          attr_reader :name
    
          sig {returns(T::Array[Employee])}
          attr_reader :employees
    
          sig {params(token: String, name: String).void}
          def initialize(token, name)
            @token = token
            @name = name
          end
    
        end
    

Disclaimer, I used Sorbet while I was an employee at Stripe. I found it to be
a terrific typechecker. It's also just absurdly fast (most of the time).

[0] [https://sorbet.org](https://sorbet.org)

~~~
cheez
This syntax is horrible. I'm surprised they didn't just copy Python's typing
syntax.

~~~
Trasmatta
I believe one of their guiding principles was that they wanted all the syntax
to be valid Ruby, because they did not want it to become a separate Ruby
interpreter. So they were pretty limited in the syntax available to them.

~~~
cheez
I'm not sure a separate interpreter is necessary but a preprocessor could
remove the notations perhaps.

~~~
Trasmatta
I believe they don't want to just strip out the annotations because Sorbet
also does run time type checking. So to get all the features they wanted, they
had to either write a new interpreter or use valid Ruby.

~~~
cheez
OK, take non-ugly syntax, translate to ugly syntax.

------
welearnednothng
It's worth noting that while the article is coming from Square, this is an
official Ruby project and is "Ruby 3’s new language for type signatures".

[https://github.com/ruby/rbs](https://github.com/ruby/rbs)

~~~
rattray
Yeah, I was wondering why this was being announced on Square's website. Seems
it's because Square happens to employ Soutaro Matsumoto, who wrote the post
and is also the creator of Steep[0] (an implementation of a typechecker for
RBS files).

It's not clear to me whether Soutaro is a member of the Ruby core team, so it
feels a bit odd that the post is written like an announcement from the Ruby
maintainers.

[0] [https://github.com/soutaro/steep](https://github.com/soutaro/steep)

~~~
baweaver
Soutaro is indeed a code member of the Ruby team, he also happens to work at
Square. Soutaro is also one of the main contributors to RBS and helped define
that standard.

He was going to keynote on this at RubyKaigi this year until it was cancelled,
and had a talk at RubyConf as well on this.

~~~
rattray
Thanks for clarifying! It's great that Square is funding work like this.

~~~
baweaver
Yep, and glad to see the work Stripe is doing on things as well. Always enjoy
seeing where you all are going.

Soutaro has been great to work with over here (Square), and he has a ton of
really amazing things coming soon that we're working on OSS'ing later.

~~~
rattray
> you all

Just to clarify, I left Stripe several months ago (and did not work on ruby
infra), so we both get to cheer from the sidelines!

------
muglug
Can someone explain why the types cannot live in Ruby code itself (after an
appropriate version bump)?

Python 3 incorporated types into the language itself, in a similar way (though
non-reified) to PHP. This seems much easier to deal with than requiring two
files (.rb and .rbs) to describe a single data structure.

~~~
skywhopper
Based on the relative smoothness of Ruby version transitions versus Python, I
trust Matz’s preference on this implicitly. One good thing about it being
external is that you can optionally and experimentally annotate existing code
without munging up your source files. At least so long as this is a bleeding
edge feature, that separation makes a lot of sense to me. It’ll be a while
before anyone can be confident in a particular model for how this should work,
until it’s been in use for a good long while.

~~~
baweaver
Pretty much. Matz is very sensitive to breaking the language in any way with
the Ruby 3 upgrade, which brought up the true keyword argument hard-break and
[likely got that pushed
back]([https://discuss.rubyonrails.org/t/new-2-7-3-0-keyword-
argume...](https://discuss.rubyonrails.org/t/new-2-7-3-0-keyword-argument-
pain-point/74980)).

RBS and type files on the side were really hotly debated for a while and the
core team settled on this as a way to not break the existing parser among
other reasons.

While I don't 100% agree with them I have faith that Matz and the team make
the decisions they do based on impact and what they see in the community.

~~~
Jabbles
His view is probably informed by the Python 2->3 experience.

~~~
jrochkind1
Ruby 1.8 to 1.9 was very painful, I am not sure why it was more succesful than
Python 2->3, I'm not sure it "deserved" to be or was any less painful on it's
face. It easily _could_ have been just as disastrous. So also informed by
that; ruby hasn't done anything nearly as painful since.

But that applies to making it so old code does not work in the new version of
the language. Nobody expects all _new_ code to work in the _old_ version of
the language. Ruby adds new features including syntax that won't properly
parse in _old_ interpreters all the time. It's not clear to me why inline type
definitions couldn't be such.

~~~
dragonwriter
> I am not sure why it was more succesful than Python 2->3

Probably because Ruby had a much narrower area where it was heavily used, with
fewer “finished” but critical libraries.

~~~
riffraff
Matz often cited the "carrot" of much better performance on 1.9 as a reason
for the successful transition.

Python3 didn't offer much over python2, so people just saw the downsides,
while ruby pushed people to upgrade with the promise that their efforts would
gain them better performance and/or save money.

------
freedomben
I'm not thrilled about the separate files with the type information but I
completely understand why they did it, and if it were my choice I might make
the same one.

I don't like the comparison with TypeScript `.d.ts` files however, because TS
still lets you do types inline in the code. I haven't seen it mentioned
anywhere that this _won 't_ be supported by Ruby 3.

Does anybody know if Ruby 3 will also support inline type information or will
the header RBS files be required?

~~~
amw-zero
I much prefer separate files for type declarations. Or at least the ability to
define them separately. Type annotation takes away from readability. I like
keeping the types and code separate.

~~~
hombre_fatal
The upside of external files is pure incremental implementation that touches
no other tooling and requires no buy-in.

I don't see how having to switch files to know that `input` is a `User`
increases readability, though. It seems like straight-forward impl-simplicity
trade-off, not one of user ergonomics.

~~~
mekster
That can be covered by the editor to give the user some hint by referencing
the external file but for the user, having have to keep adding it on a
separate file seems pretty annoying as you need to keep declarations synched
in 2 files.

Also how do you type something in an inline function?

------
recursivedoubts
Cool. And glad to see this called out:

 _Better IDE integration: Parsing RBS files gives IDEs better understanding of
the Ruby code. Method name completions run faster. On-the-fly error reporting
detects more problems. Refactoring can be more reliable!_

IDE support (autocomplete, refactoring and quick documentation) is the most
important reason to annotate argument and return types.

~~~
bytematic
I've been using typescript for a few years now and to be honest I almost never
rely on the compilation errors. I just use the built in Jetbrains IDE
completion, autosuggestion, and navigation to make it work.

~~~
recursivedoubts
Yep. A good IDE to a first approximation doesn't allow compilation errors to
occur because you are auto-completing everything, including symbol completion
based on the type at cursor, etc.

Jetbrains is a wonderful company.

~~~
smabie
Since the advent of LSP, I think the value proposition of a full featured IDE
has been greatly diminished.

For example, I used to use Intellij for Scala but recently switched to
Emacs+Metals and haven't really missed anything. In fact, it's probably an
even better editing experience.

Intellij still has better refactoring (though I don't use it much), and the
integrated debugger and database viewer are really nice. I've found myself
using Emacs and only switching to Intellij for the aforementioned specialized
tasks.

5 years ago you would have been crazy not using an IDE for JVM work but this
is no longer the case. LSP is such a wonderful technology and has empowered
the creation of new programming languages like never before. It's truly
remarkable.

If I was Intellij, I would be a little worried about my future market share.
They simply can't provide the same value as before, and I'm not sure how they
intend to change that.

~~~
jolux
I wish I had had your luck with language servers. They are fantastic when they
work but in my experience configuring them is finicky and difficult,
particularly with Emacs. I have also run into problems where the server
crashes and does not restart itself, so the IDE functionality in my editor
will just silently break and I have to go fix it. JetBrains still dominates
the market for IDEs that work out of the box, and I still don't know of any
LSPs that can even remotely compete with the sophistication of their static
analysis tools and such.

------
swagonomixxx
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).

~~~
3pt14159
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.

~~~
danudey
> 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.

~~~
vidarh
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.

~~~
danudey
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.

~~~
vidarh
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)

------
castwide
I'm still trying to make sense of this announcement. With a lack of type
annotation in the Ruby core, I chose to build off YARD to make gradual type
safety work. Now I don't know if there will be a standard that supports type
safety or if I should continue down the path I'm already following. Help me,
Ruby core developers. You're my only hope.

(edit: I should have explained that I'm talking about the type checking
features I'm developing in Solargraph: [https://solargraph.org/guides/type-
checking](https://solargraph.org/guides/type-checking))

~~~
transfire
I hear you. It occurred to me that Ruby could have chosen to innovate with
something like a Semantic TomDoc. To choose a separate file based approach
seems like a step backward. At the very least it could have been module based.
But Matz is a C coder -- not a Ruby coder. So it doesn't necessarily surprise
me.

It's sad though. Since poor design of Refinements, C transpiling for 3x
project, and now this, I am less and less inclined to continue using Ruby. I
miss some of the dynamics but I find myself using Crystal instead.

(Honestly, if any one figured out a way to supplement Crystal with dynamic
behavior for those features that a static language can't offer, Ruby would be
done.)

~~~
d3nj4l
As soon as I feel comfortable maintaining a Crystal server in production I
think I'll switch to it. Last I tried it, things broke and shards required
some effort to maintain every version update. I'm eagerly looking forward to
their 1.0 and hoping they stabilize a lot more.

------
vidarh
The article's use of "typed vs untypes" instead of "statically vs dynamically
typed" is really unfortunate, and doesn't exactly inspire confidence.

~~~
MaxBarraclough
Indeed. To expand on your point: yep, it's incorrect. An untyped language is a
language in which there is no concept of type. Assembly languages tend to be
untyped. Forth is untyped.

The Ruby and Python languages do have the concept of type, it's just that
they're dynamically typed, not statically typed. They check types at runtime.

~~~
iso8859-1
But when you have things like "duck typing", don't you think "they check types
at runtime" becomes less meaningful? The majority of functions written in
Python, even the ones that have type annotations, do not effectively have
"assert isinstance(...)" in the program text below their signature, which is
what I'd expect after reading "check types at runtime".

Also, Python now has (in its stdlib!) things like typing.Protocol, which is
almost exclusively checked at type checking time. So if such a thing exists,
and you still say "types are checked at runtime", isn't that confusing?

~~~
vidarh
I don't really know what you're trying to say here.

Why would it be less meaningful to say types are checked at runtime with
ducktyping? The nature of ducktyping is that the specific class of an object
does not matter relative to behaviour, but a class is not entirely equivalent
to a type.

If I need an object that implements method `foo`, and don't care about class,
then "objects that implements foo" is in itself a type, that can potentially
be inferred and checked be it at runtime or before.

> The majority of functions written in Python, even the ones that have type
> annotations, do not effectively have "assert isinstance(...)" in the program
> text below their signature, which is what I'd expect after reading "check
> types at runtime".

You're thinking his "checks types" too narrowly. Every time I try to call a
method on an object in a strongly typed language, typing is involved. It
doesn't so much "check" it as _look up the method_ to see whether this method
applies to this specific _object_ _at this point in time_ and decide whether
or not to throw exceptions.

But the point remains that it is a typed. And _strongly_ so - in both Ruby and
Python objects has a type associated with the object itself, unlike e.g. C or
C++ which are weakly typed because it is the _variables_ that are typed, not
the values.

~~~
MaxBarraclough
Quibbles with your final paragraph: that's not what _strongly typed_ means. It
refers to when a language has strict rules restricting implicit type
conversions. [0] Also, C++ has RTTI.

[0] [https://learn.adacore.com/courses/intro-to-
ada/chapters/stro...](https://learn.adacore.com/courses/intro-to-
ada/chapters/strongly_typed_language.html)

~~~
vidarh
It's not that simple. There's no uniform definition of strong. You're right
strong typing is often used to refer to absence of (or restrictions to)
implicit type conversions, but it has also since the beginning been used to
reference languages that does not prevent obscuring the identity of the type
of an object.

E.g. Liskov and Zilles [1] defined it this way for example:

"whenever an object is passed from a calling function to a called function,
its type must be compatible with the type declared in the called function."

Under this definition C and C++ fails hard, since you can statically cast a
pointer to an object and pass it to a function expecting a totally
incompatible type.

Note that the system described relied at least partly on dynamic/runtime type
checks (in case it reads as if the quote above suggests they used "strong" to
refer to static typing):

"It is desirable to do compile-time type checking, since type errors are
detected as early as possible. Because of the freedom with which types can be
used in the language, however, it is not clear how complete the compile time
type checking can be. Therefore, the design of the language is based on a
runtime type checking mechanism which is augmented by as much compile-time
checking as is possible. "

[1]
[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.13...](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.136.3043&rep=rep1&type=pdf)

~~~
iso8859-1
If C++ fails when you do casts, why doesn't Haskell fail since it allows
infinite recursion to build terms of whatever type you like? You can make the
same argument: "don't do that". C++ compilers can warn when you cast. Haskell
compilers can warn on incomplete pattern matches.

Is the definition you use not so broad as to admit all invariants being
described as types?

If a method requires a dictionary arguments with a certain key, is that a type
to you? If you extend the term "type" to cover all invariants, I don't think
that is the way that the term is commonly used, even though many invariants
can be proven in e.g. dependently typed languages.

All these terms are so wonky because a C++ type is not equal to a Haskell
type. So I feel we can't ever have solid definitions of terms like "strong",
since it depends what you compare it to, and it also depends what you compare
from. So while X is strong in comparison to Y, that doesn't say much about X's
relation to Z.

------
jrochkind1
I don't understand the benefits of forcing it to be in a separate file. I'd
rather at least optionally you could include the types in the source file
where you defining the methods.

Being able to define an interface instead of pure un-specified "duck type" is
great.

------
stewbrew
Separate ruby header files with type information? Seriously? What's the
rational behind that? Is it just to make clear that the ruby interpreter
doesn't really care about the type information and doesn't use it to improve
the code's performance?

With all due respect, but IMHO this is too little and much too late.

~~~
saghm
Isn't this similar to how TypeScript allows annotations for JavaScript to live
in separate files? My (possibly naive) assumption is that the goal is to make
it easier for developers to write type annotations for projects they use
without necessarily having to convince the maintainers to add them to the
project itself. I've heard of people using TypeScript annotations from
[https://definitelytyped.org/](https://definitelytyped.org/) for dependencies
which don't have their own annotations.

~~~
TomMarius
.d.ts files are mostly compilation output, only if the original source is
JavaScript you write the definitions by hand. TypeScript developers normally
work with types inside their ordinary .ts (JS+types) module file, not in a
separate source file.

------
danfritz
How does this compare to the sorbet project? Is it two different
implementations for the same goal or is it adding support in ruby so sorbet
can also benefit?

~~~
zrail
The sorbet project has an FAQ about that (I was curious too):

[https://sorbet.org/docs/faq](https://sorbet.org/docs/faq) search for “RBS”
(on mobile, no deep links)

~~~
ghiculescu
[https://sorbet.org/docs/faq#when-ruby-3-gets-types-what-
will...](https://sorbet.org/docs/faq#when-ruby-3-gets-types-what-will-the-
migration-plan-look-like)

------
AaronFriel
I likely am missing some context, but the comparison to TypeScript's ".d.ts"
files seems misplaced. This is a type signature language, but it does not seem
to be a type checker.

The comparison to .d.ts files then seems bizarre because that is helpful for
language servers¹ to consume types, but there's no proof that say, the
implementation matches the type specification.

TypeScript declaration files _declare_ what the types of module exports are.
For the most part, a .d.ts file informs the typechecker "the type of module
Foo export bar is the interface named Quux". This is not _checked_ , this is
simply an assertion. The language server for TypeScript will pick these
definitions up, assume they are correct, and provide code completions for
those types as if they were correct.

On the other hand, a .ts file, combining types and codes, enables type
_checking_. If the type declarations are incompatible with the code, an error
is thrown by TypeScript. While .d.ts files _declare types_ , .ts files verify
that the code and the types declare are compatible.

Since .rbs files simply describe the external interface of types and modules,
and cannot describe internal variables, I'm not sure how it's doing any type
_checking_.

For example, if I have this code:

    
    
        module Foo
          class Bar
            def trivial: () -> String
          end
        end
    

What prevents me from writing this:

    
    
        class Bar
          def trivial
            return 42
          end
        end
    

Or alternatively, this:

    
    
        class Bar
          def trivial
            x = some_function_that_might_not_be_declared_in_an_rbs_file()
            return x
          end
        end
    

Does x have a type? Can I gradually type "class Bar" via type inference, or do
I have to declare _and keep in sync_ all my rbs files with my rb files? What
happens when the rbs file is out of sync with the rb file?

¹ Language servers are implementations of an IDE protocol for code completion.
The trend in programming language tooling is to use Microsoft's Language
Server Protocol ([https://microsoft.github.io/language-server-
protocol/](https://microsoft.github.io/language-server-protocol/)) to provide
code completion, semantic navigation, refactoring, etc.

~~~
rattray
The type checker implementations (eg; Steep, Sorbet) should throw an error at
`return 42`. If `some_function_that_might_not_be_declared_in_an_rbs_file` is
not known, the behavior may be configurable – it could be assumed to be
something like `any` or assumed to be unsafe, and error until you declare that
function. I think Sorbet has this configurability today, and I'm not sure
about Steep's behavior.

Steep: [https://github.com/soutaro/steep](https://github.com/soutaro/steep)

Sorbet: [https://sorbet.org](https://sorbet.org)

------
Lammy
It’s a separate RBS file for now, but if/when this gets integrated into Ruby
itself how will it interact with the new Ruby 2.7/3.0 keyword argument syntax
that also uses the colon? [https://bugs.ruby-
lang.org/issues/14183](https://bugs.ruby-lang.org/issues/14183)

~~~
jrochkind1
note that syntax isn't actually new in ruby 2.7 or 3.0 at all. It's been
around since ruby 2.0 in fact (Feb 2013). (keyword arguments had to be
declared with default values until ruby 2.1, Dec 2013).

What ruby 2.7/3.0 do is rationalize some really weird counter-intuitive and
ambiguous edge cases related to passing a Hash arg expecting it to be invoked
_as if_ it were keyword args. But it's a change in semantics, not syntax. The
keyword argument _syntax_ , keyword arguments with colons, in both method
definitions and invocations, has been around for years, unchanged.

------
RangerScience
I'm really loving how this is intended as a starting point, so that the
community can continue to build and explore on top of it. Feels very Ruby Is
Nice, So We Are Nice.

------
exabrial
Types help communicate information to the next developer. They're important,
use them!

~~~
sparker72678
This isn't just about Types, _per se_. Ruby has types all over. You can't
write Ruby without them.

This is about Static Typing and Type enforcement, in the context of a language
that holds flexibility and meta-programming as high values.

------
vemv
Checking that something is a String is pretty weak and uninteresting.

How about checking that a given string is non-blank?

That tends to fully leverage Ruby's dynamic nature.

But then again people are overly fixated with compile-time, Java-like
signatures.

See clojure.spec for a success story.

~~~
sixstringtheory
> How about checking that a given string is non-blank?

Because when you try to do this with some object that doesn't have a "length"
or "empty?" method, your application crashes.

    
    
        irb(main):013:0> a = 1
            => 1
        irb(main):014:0> b = "1"
            => "1"
        irb(main):015:0> b.length
            => 1
        irb(main):016:0> a.length
            Traceback (most recent call last):
                4: from /usr/bin/irb:23:in `<main>'
                3: from /usr/bin/irb:23:in `load'
                2: from /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
                1: from (irb):16
            NoMethodError (undefined method `length' for 1:Integer)
        irb(main):017:0> b.empty?
            => false
        irb(main):018:0> a.empty?
            Traceback (most recent call last):
                4: from /usr/bin/irb:23:in `<main>'
                3: from /usr/bin/irb:23:in `load'
                2: from /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
                1: from (irb):18
            NoMethodError (undefined method `empty?' for 1:Integer)
    

This is why people want a way to know if something they think is a String is
actually a String, without risking data loss and outages at runtime.

I think it's rude to dismiss people asking for this as "fixated," and
furthermore it could be no less fairly used against people who show up to
these debates beating their own drum against it.

~~~
vemv
One can check that something is a string _and_ not a blank one.

There are two _different_ libraries that do it:

[https://github.com/plumatic/schema/blob/ddb54c87dea6926c6d73...](https://github.com/plumatic/schema/blob/ddb54c87dea6926c6d73542e517b53685cc82a37/src/cljx/schema/core.cljx#L594)

[https://github.com/clojure/spec.alpha/blob/eb49d429e85b6878a...](https://github.com/clojure/spec.alpha/blob/eb49d429e85b6878a61443e853be26092ff6e249/src/main/clojure/clojure/spec/alpha.clj#L495)

Honestly I highly suspect that many, many "typing" solutions out there are
plain ignorant of the whole spectrum of choices one can make, are tend to lean
towards Java-like APIs out of that ignorance.

This is not limited to Ruby, I also see it in TypeScript which is very much a
contrived system for real-world usages while making little use of JS's
dynamism.

~~~
sixstringtheory
I'm glad we agree that adding type annotations to code is a good thing. You
aren't arguing against that–you are just arguing implementation details. If
I'm designing a 5 9s service tasked with processing gigabytes of data every
unit time, I don't want to pay the runtime cost of checking the types for
every mundane operation like measuring the length of a string.

~~~
vemv
Design by contract (which the mentioned libraries excel at) isn't a new thing,
and the mentioned one is a solved problem.
[https://en.wikipedia.org/wiki/Design_by_contract#Performance...](https://en.wikipedia.org/wiki/Design_by_contract#Performance_implications)

~~~
sixstringtheory
It is evidently not a solved problem. Why would large financial services
companies be working on these things we're talking about if that were the
case?

Do you know what solves the problem of checking every type of every thing you
want to use before you use it, where the checks don't incur any performance
penalty at runtime? Static type checkers!

Modern compilers usually don't even require any explicit type declarations, as
they can infer at assignment. So they provide more safety for less code than
your examples.

------
hartator
> Typed languages are suitable for larger projects but are often less
> flexible. Untyped languages allow for rapid development, but scaling teams
> and codebases with them can be difficult.

Well some of us disagree with the statement that untyped is not suitable for
large teams. And that's why we use Ruby. There is a lot of very good typed
languages out there if you do want typed. I feel Square and Stripe are pushing
their own codebase issues onto general Ruby - as it's our problem to solve -
which is not cool.

------
azinman2
Interesting. I’m surprised they didn’t opt to do this inline with the rest of
the ruby code, because now they can diverge from each other. It’s a bit like a
separate header file in C/C++/Obj-C, except in those cases the compiler will
yell at you if the implementation doesn’t match the header. Having it blow up
at runtime instead doesn’t feel like such a big change from the way it is now,
other than helping out IDEs.

~~~
CarelessExpert
> I’m surprised they didn’t opt to do this inline with the rest of the ruby
> code,

As they mention in the post, they followed typescript's approach, here. The
benefit is it allows you to layer in typing into an existing codebase in a
non-disruptive way.

~~~
azinman2
But as I mentioned the downside of this is that any mistakes don't become
evident until at runtime. While the python way has the same problem (they're
not compiled languages after all), by inlining to the existing source there's
less changes for divergence to happen.

~~~
ativzzz
Remember this is designed by companies that already have existing, large, ruby
codebases. For them, it makes a lot of sense to be able to incrementally add
typing without having to make changes to the underlying code itself.

~~~
joshuamorton
You can do both. Python allows for external .pyi files, for situations where
you can't modify the underlying library (for example: it's written in C).
There are tons of them:
[https://github.com/python/typeshed](https://github.com/python/typeshed), but
you can still add types to new code inline.

~~~
ativzzz
You can, but clearly the people designing this system have weighed the pros
and cons and found that there would be more benefits to them to leave the
source code unchanged.

~~~
baweaver
Yeah, there was a ton of discussion on this in the Ruby bugtracker and at core
meetings. Matz is very sensitive to breaking the language with Ruby 3 and the
core team is doing their best to ensure an easy transition.

------
ch_123
> Typed versus untyped is a 30-year-old issue for programming languages.

I'm pretty sure the merits of typed vs untyped has been going on since the
1950s at least. 30 years is such a specific period of time that it makes me
wonder what happened in the early 90s that the author is referring to.

~~~
cschep
Ruby was invented? :)

~~~
HideousKojima
And JavaScript and Python, and a whole host of languages that didn't care
about strong typing

~~~
ch_123
None of these were the first untyped languages, or even the first widely used
untyped languages.

~~~
vidarh
In fact none of them are "untyped". They are strongly typed.

BCPL could conceivably be described as untyped.

------
davidkellis
At some point it makes more sense to switch to Crystal.

~~~
loktarogar
Crystal is pretty unstable at the moment (in that it changes often). I have a
very small project in crystal (discord bot with only a couple of commands that
CRUDs a database). Every time I deploy to heroku, if heroku updated crystal,
the bot breaks. I have to spend an hour determining what broke. The language
changes _so often_ that either I update with heroku or I can't use any newer
libraries for development.

And then you get stuck in very opaque error loops with the typing where it's
expecting eg. a Number. no, not that number, a different type of number. no,
not that type either. no, not that type. Most the code i've written has been
typecasting.

Crystal does have some significant benefits over Ruby (which is why i'm using
it - better memory use, better for scaling for my purpose) but I just spent
time rewriting the non-user facing, non-scaling part of the bot in Ruby so I
could actually get stuff done instead of fighting the language.

Of course a lot of this might be my inexperience with Crystal, but as a Ruby
dev for 13 years, it's not as easy as just switching over from Ruby to
Crystal. I've had this bot running 4 years and it hasn't got any easier for
me.

~~~
3by7
> Every time I deploy to heroku, if heroku updated crystal, the bot breaks

Have you tried adding a `.crystal-version` file as described in the
buildpack's README? [https://github.com/crystal-lang/heroku-buildpack-
crystal#cry...](https://github.com/crystal-lang/heroku-buildpack-
crystal#crystal-heroku-buildpack)

~~~
loktarogar
Yeah, the issue is more that I can either keep Crystal updated and use all
libraries, or keep it frozen and be restricted in what I use. It's not mature
enough yet to be able to lock it down and have more or less Crystal's entire
shard collection available to me, like I can do in Ruby. Reminds me more of
Ruby a decade ago with 1.8.7 and 1.9.2 and 2.0 (which is fine for it to be at,
esp for an evolving language, but it's not "there yet" to be a replacement for
Ruby)

One incident that stands out is that certain Postgres support was only
available in the latest version of a shard, which required the latest version
of Crystal, which wasn't compatible with 3 of the other shards I was using.

------
xmdx
I guess this is just the foundation. A way to check types before runtime. I
can see a lot gems making this experience better so I'm not too worried about
that. It will become better, and it is optional.

For the time being I think this kind of type checking is only worthwhile in
big projects, for smaller projects I have found Sorbet never finds an error,
so it's just extra work to generate the files on a big change.

------
Lio
I use unit tests as a design and documentation tool.

Since we already have a specification tool (MiniTest) in the StdLib it would
interesting if we could combine the rbs files with spec unit tests.

I already have my matching test file open anyway. Having the typing
information in the same place would encourage the use of types, tests and
documentation.

------
hirundo
Anyone know, or have a guess, what RBS stands for? I'm not finding it in the
article or library.

~~~
allknowingfrog
I'm seeing the type annotations being referred to as "signatures" in a couple
of places. (R)u(B)y (S)ignatures seems like a reasonable guess.

~~~
baweaver
That would be correct.

------
satisfaction
I wonder if this type information can be used by the vm to improve
performance?

~~~
baweaver
Matz had mentioned this as a key reason he was interested in working on this,
as well as a "language server" with a lot of really interesting features like
what JS/TS has with VS Code.

I had the good fortune to hear him talk about it at length at a conference a
while ago and there's all types of fun stuff on the way.

------
lovetocode
As an avid Rubyist I have no interest in introducing types into a dynamic
language. I would just rather use C# or Java. I never understood why people
are trying to make a round peg fit in a square hole.

~~~
symlinkk
It’s insane to me that people are still arguing against static types.
TypeScript has proven that you can add the safety of static types without
taking away any of the flexibility of dynamic typing. Every time you dive deep
into source code to figure out what a field is called or what a function
expects, remember that you could have eliminated that completely with static
types.

~~~
mekster
Perhaps if people have not used JS and TS, they would think that typings are
in their brain memory but typings are a lot more than scalar and return value
typings like defining a structure of a hash and apply that dynamically
depending on parameter value or give string an enum like behavior limiting
what could be assigned.

------
Vanit
> Untyped languages allow for rapid development

Citation needed. In my experience this is a fallacy; types take no time at all
to write and significantly reduces bugs (Typescript).

------
bytematic
I just want typescript for ruby. That simple

~~~
mekster
With all the prior arts, I'm not sure how ruby didn't pick up the good parts
and made it better than others.

------
cheez
I prefer Python's typing module to this.

------
alexpapworth
Really exciting!

------
dzonga
best type-checking system in my experience has been F#. You don't even have to
declare types.

------
transfire
No multimethods for you!

------
monadic2
How does this interact with method_missing, the soul of rails? Obviously much
of the rails api is defined dynamically, not statically. I could see runtime
checks having some value but I’m not sure how an IDE could take advantage of
this, period. I’d imagine you’d at least need to generate methods (rather than
parse and route messages at runtime) to make this remotely viable.

I’m not super familiar with ruby outside of my work so I’m not sure if this
reliance on method_missing is more widespread than rails.

~~~
avolcano
You're correct in your assumption you'd need to generate methods, from my
quick investigation into how Sorbet handles it - see
[https://github.com/chanzuckerberg/sorbet-
rails](https://github.com/chanzuckerberg/sorbet-rails) for some details.

------
pjmlp
They would do better contributing to Crystal, but what do I know.

------
phjesusthatguy3
RBSup dawg. I heard you like type signatures, so I created a file format
called .rbs, so you can alt-tab while you code.

~~~
mekster
More like ctrl-tab in the same editor.

------
didip
It has been so long since I last heard activities on Ruby. I had assume people
moved on to Go or Rust for high traffic work by now.

~~~
ativzzz
> for high traffic work

Considering how prevalent Rails is for Ruby, we can assume that the majority
of Ruby codebases are web apps. There are tons of way to scale Ruby web apps
for high traffic, and a tiny minority of companies end up reaching a scale
(like Twitter) where Ruby becomes infeasible.

------
burke
I absolutely hate this.

Separate files for types with no inline annotations possible? What an
embarrassing compromise. This is all because Matz explicitly won't allow type
signatures in .rb files. I wonder how long it'll be until a hostile fork if he
doesn't change his mind.

~~~
amw-zero
I wouldn’t call it “embarrassing.” What is the actual benefit of having inline
type annotations? What is the actual downside of having them in a separate
file?

~~~
mekster
How hard is it to imagine that you need to keep 2 files in sync, and that you
can't type anything inside a method.

I was even hoping to use ruby as a main language having used it before but I'm
about to lose any interest in the language when its reality is a bit decoupled
from the rest of the world.

~~~
amw-zero
Have you ever worked with a language that has header files (C/C++) or a
language that can use them optionally (Ocaml)? In practice, keeping the files
in sync isn’t difficult. In fact, it ends up being better (for me) in terms of
readability, because I can look up the type definitions in one place, store
them as context, and then read code that isn’t littered with type annotations.
Type annotations add quite a bit of noise to code. I think that’s what Matz is
going for here. You need to be able to keep the readability of Ruby, which
he’s dedicated his life to.

~~~
Jweb_Guru
Not to anecdote too hard, but the practice of doing type signatures out-of-
line that you are describing is my absolute least favorite part of OCaml,
which is otherwise a very lovely language. As for C++, it has enough other
stuff going on that I probably can't say the header files are my _least_
favorite feature, but they certainly don't make life easy. I think it makes
sense to challenge that decision from an ergonomics standpoint, even if it
makes sense within the constraints the Ruby core team has decided upon.

~~~
amw-zero
I very much like Ocaml signature files. Jane street recommends using them, as
an example. There’s no other way to communicate the high level contract of a
module. With type signatures that are in line with the code, the high level
contract gets lost.

