[HN Gopher] A few words on Ruby's type annotations state
       ___________________________________________________________________
        
       A few words on Ruby's type annotations state
        
       Author : todsacerdoti
       Score  : 105 points
       Date   : 2023-05-05 16:58 UTC (6 hours ago)
        
 (HTM) web link (zverok.space)
 (TXT) w3m dump (zverok.space)
        
       | munificent wrote:
       | My experience is that you can have powerful runtime
       | metaprogramming, or you can have good static type checking, but
       | you can't have both.
       | 
       | In most of the larger programs I've worked in the value of static
       | typing is immense. In terms of my productivity, the only
       | programming language feature that's possibly had a bigger
       | positive impact is garbage collection.
       | 
       | Runtime metaprogramming is very cool, but I've yet to work in a
       | codebase where I felt that the value of it was greater than the
       | value I get from good static analysis.
       | 
       | One way to think of static types is that they're a debugger that
       | _simultaneously debugs every possible run_ of the program. And on
       | top of that, you also get code navigation and better performance.
       | It 's hard for me to imagine a sufficiently great metaprogramming
       | feature that would be worth giving that up.
        
         | bobbylarrybobby wrote:
         | I feel that oftentimes runtime metaprogramming is to make up
         | for code that can't be written down statically. But if you have
         | the right macros then you don't need the metaprogramming to
         | happen at runtime. For instance, Rust has a pretty solid type
         | system (not quite Haskell, but more intricate than Java) _and_
         | lets you do things like generate serialization and
         | deserialization code for arbitrary structs at compile time,
         | which most languages would do at runtime by inspecting fields
         | or something similar.
         | 
         | All this to say, runtime metaprogramming doesn't seem
         | inherently more capable compared to what's possible in the more
         | "static compiler" languages; rather, it seems like an escape
         | hatch for when you can't generate the code you want at compile
         | time.
        
           | munificent wrote:
           | Yes, _compile_ -time metaprogramming is a different approach
           | that I'm personally really interested in. There are trade-
           | offs as always, but it can give you much of the power of
           | metaprogramming without totally killing static analysis.
        
         | syspec wrote:
         | Java honestly provides both, and it does it extremely well! I
         | personally prefer to use Kotlin, but same idea.
        
           | paulddraper wrote:
           | > Java honestly provides both
           | 
           | I think there is a serious disconnect about what is
           | "metaprogramming."
        
             | kccqzy wrote:
             | Agreed. I was going to say Haskell does both very well, but
             | that's impossible to agree or disagree without first
             | agreeing on a definition for metaprogramming.
        
           | munificent wrote:
           | Java can do some fairly powerful stuff with reflection and
           | classloaders, but isn't anywhere near the level of runtime
           | flexibility you can get with Smalltalk or Ruby where the
           | entire running program is a mutable data structure you can
           | programmatically introspect over and modify.
        
       | RangerScience wrote:
       | Took me a bit thinking through it all, but I arrived at the same
       | place as the author in the end - use the pattern matching system,
       | and if the types don't match then the block you're dispatched to
       | just raises an error. The rest is just finding a satisfying
       | syntax.
        
       | al2o3cr wrote:
       | An observation about the discussion immediately under "In fact,
       | we in Ruby have several incomplete systems of type-annotations-
       | in-disguise" - to me, that list indicates that fitting all those
       | things into a SINGLE type system is a huge challenge. The spaces
       | of _possible_ types for a JSON API, an ORM, and an interactor 's
       | inputs are all different.
        
       | ecshafer wrote:
       | I do not like Sorbet, and I don't think ruby's type annotation is
       | in a great spot. The claims of Sorbet to allow easy refactoring
       | are just not there imo, and do not reach the level of Java.
       | Sorbet if anything involves more time fighting sorbet, and you
       | still rely on tests for validation. Steep looks better, but I
       | think it needs to be brought in file.
       | 
       | Personally if we do bring types, I would like to see an optional
       | typescript like definition
       | 
       | def foo(:String x) -> String
       | 
       | or similar.
        
       | mbell wrote:
       | The biggest issue with current attempts at typing in Ruby is the
       | choice of a nominal type system. If there ever was a language
       | that called for structural typing, it's Ruby.
        
       | RcouF1uZ4gsC wrote:
       | I am not convinced of the utility of how good type annotations
       | are in the long term.
       | 
       | I think static typing is a very fundamental part of language
       | design that affects the entire language. To make static typing
       | ergonomic without massive boilerplate, you also need concepts
       | like generics, co and contra-variance, type inference, and other
       | things that dynamically typed languages never have to really
       | concern themselves with.
       | 
       | When you try to bolt it on afterwards, I think you can end up
       | with a lot of foot guns and corner cases and a false sense of
       | security.
        
         | LesZedCB wrote:
         | i personally really like the idea of what clojure has done with
         | spec or malli instead of type annotations. it just _feels_ to
         | me more like what rubyists want: pragmatic validation of  "does
         | this quack?" but like you say, i'm not sure ergonomics can be
         | brought up to the level of ease/happiness to be used regularly
        
         | lloeki wrote:
         | > I am not convinced of the utility of how good type
         | annotations are in the long term.
         | 
         | Anecdata: adding typing (via Sorbet, then RBS) to multiple
         | projects over the years uncovered many corner case (and not-so-
         | corner case but non obvious) bugs, some subtle but critical,
         | before they ended up crashing production.
         | 
         | I can vouch with real life experience that it is useful.
         | RBS+Steep is so useful I inadvertently found myself+ beginning
         | to write type-first, describing my code in .rbs files then
         | filling in the implementation. Thinking in types has helped me
         | uncovered issues right at the idea stage when I could have
         | produced a smart^Wfatally flawed implementation that would
         | still work thanks to Ruby's dynamism (mind you, Ruby's dynamism
         | is great, it is merely a tool to be wielded at appropriate
         | times)
         | 
         | + Which is kinda useful as a "ok this thing works" rule of
         | thumb in my book
        
         | akavi wrote:
         | Have you used typescript? Because to my eyes, that's a
         | _massively_ successful  "bolted on" type system.
         | 
         | Sure there're holes in the system, but the utility is
         | significant, especially relative to the cost.
        
         | itake wrote:
         | I've worked in multiple systems written in python, ruby, and JS
         | where an untyped dict|hash|object is passed from function to
         | function. In each function, keys are read; keys are written;
         | keys are deleted. Tracking params were actually called by each
         | function was a nightmare.
         | 
         | If this "magic" dictionary was sourced from a schemeless api or
         | database, it was impossible to figure out the schema of this
         | dict without running the code in production.
        
         | codr7 wrote:
         | Yeah, I just duplicated a Go method because it wouldn't accept
         | a typed vararg as untyped (...any), and methods can't be
         | generic either. I would rather not have static types at all
         | than a not-quite-complete type system. I find Common Lisp's
         | gradual approach to types very convenient.
        
       | hhthrowaway1230 wrote:
       | Ruby needs opt-in inline typing. Much like python. Typing is a
       | language or dsl of what u expect to receive as input and as
       | output. Its a communication mechanism. Leaving out ambigiousness.
       | the job for ruby is to make it not a hassle but a joy to use.
        
         | lloeki wrote:
         | I disagree with inline. It obscures the code, especially with a
         | language as terse as Ruby. That's a code editor job to present
         | me with types appropriately within its UI (which may be
         | tooltips, autocompletion, virtual text that looks like comments
         | or annotations+...)
         | 
         | Having types in separate .rbs files has proven to me to be
         | perfectly usable, and in fact better in many ways.
         | 
         | Sorbet's DSL of having `sig { }` present is the worst kind of
         | annotation, as it needs to pollute `Kernel` with `#sig`, even
         | when you disable runtime checking.
         | 
         | This is a no-go if you develop anything else than an app (e.g a
         | gem) as you certainly don't want to push Sorbet++ as a
         | dependency to your gem consumers, not the least because
         | `sorbet-static`++ is still only available for `x86_64`. In
         | general Sorbet seems to be designed to type apps, gems being an
         | afterthought.
         | 
         | + https://github.com/jubnzv/virtual-types.nvim
         | 
         | ++ or include a monkyepatch that defines an empty `Kernel#sig`
         | 
         | ++ https://rubygems.org/gems/sorbet-static
        
         | ezekg wrote:
         | Did you read the article? It discusses this topic and how it's
         | a hard problem in Ruby vs. Python due to Ruby's syntax choices.
        
       | sproketboy wrote:
       | [dead]
        
       | uticus wrote:
       | > In my OSS and blogging activities, I am mostly focused on the
       | same concepts: Ruby intuitions and lucid code...the main question
       | is always "how the language leads us to express this in the
       | clearest way possible."
       | 
       | Unfortunately this article itself needs some editing. I stuck
       | through to the end, but it was not enjoyably lucid.
       | 
       | > I honestly tried to perform it alongside the reader: since the
       | early drafts of the article, my expectations were that I'd come
       | up with some coherent idea about a possible Ruby typing syntax.
       | My attempt resulted in a deeper understanding of the level of
       | design complexity making it hardly achievable.
       | 
       | Plus one for trying it out, I guess. The temptation is there,
       | because Ruby is so flexible. "It works so well for DSLs, maybe it
       | can be expanded to include typing!" The trouble is, the added
       | syntax rules to support typing will never stay in the sweet spot
       | between "enough in the foreground to assist coding" and "enough
       | in the background to not distract from the coding flow." You
       | either live without it, as in Ruby, or learn to mentally parse
       | over it, as in other languages with the Func(String s, Int i)
       | syntax.
       | 
       | For myself, I'm fine with the typing being in a separate .rbs
       | file. Especially if my IDE supports it well.
        
         | nine_k wrote:
         | > _this article itself needs some editing._
         | 
         | Possibly! But likely the reason is this:
         | 
         | > _I am writing this on my phone, in a barrack that houses some
         | 200+ of my brothers-in-arms in the Ukrainian army's training
         | camp; I use short periods of rest between training, mostly at
         | night and on Sundays._
         | 
         | I won't expect a lot of text-polishing opportunities in
         | conditions like that. I'd be grateful for any coherent and
         | insightful output, like the article.
        
         | lloeki wrote:
         | > For myself, I'm fine with the typing being in a separate .rbs
         | file
         | 
         | We type[0] by having one separate .rbs file per .rb file. Works
         | really well with an editor's vertical splits: type outline on
         | one side, code on the other. That, or use something like vim-
         | projectionist[1].
         | 
         | We have a static typing guide for contributors[2], and I'm also
         | accreting common errors and issues behind the scenes to produce
         | a 'rbs+steep by example' to ease folks in.
         | 
         | [0]: (WIP: there's a huge codebase to type, but we're
         | progressively getting there) https://github.com/DataDog/dd-
         | trace-rb/tree/master/sig
         | 
         | [1]: https://github.com/tpope/vim-projectionist
         | 
         | [2]: https://github.com/DataDog/dd-trace-
         | rb/blob/master/docs/Stat...
        
       | egonschiele wrote:
       | I had written a code contracts library for Ruby about 10 years
       | ago [1]. I stopped working on it, mainly because it only provided
       | runtime type checking, and I wanted static type checking.
       | Nowadays my main language is typescript. I miss ruby, but can't
       | give up the static typing that typescript provides. I really wish
       | Ruby had a type system with the same level of support. VSCode has
       | phenomenal TS support, and there's a community adding types to
       | projects [2]. This is something I'd like for Ruby also.
       | 
       | > An integral part of this informality is relying on Matz's taste
       | and intuition for everything that affects the language's core.
       | 
       | I think a more defined process would mean a better future for
       | Ruby and Ruby developers.
       | 
       | - [1] https://github.com/egonschiele/contracts.ruby
       | 
       | - [2] https://github.com/DefinitelyTyped/DefinitelyTyped
        
         | lloeki wrote:
         | > it only provided runtime type checking
         | 
         | That's the big nail in the coffin. Except for RBS and Steep, I
         | don't know of any that does/did static type checking.
         | 
         | And yup, Sorbet's static type check is very partial to the
         | point they recommend enabling runtime type checking.
         | 
         | Also in its usage, Sorbet needs to _evaluate_ files. Too bad if
         | one of them was a script that included `FileUtils.rm_rf`. Ok
         | I'm going overboard (maybe?), but Sorbet is not stable in face
         | of code that has side effects when the file contents is
         | evaluated.
        
           | eric-hu wrote:
           | > Also in its usage, Sorbet needs to _evaluate_ files.
           | 
           | Are you saying this in the context of static analysis or
           | runtime analysis? I'm pretty sure Sorbet does not need to
           | eval files for static analysis.
           | 
           | > Ok I'm going overboard (maybe?)
           | 
           | You are going overboard. I like both TS and Sorbet. Neither
           | type system will protect you from running malicious code on
           | your computer.
        
         | bilekas wrote:
         | I'm genuinely curious, why do you miss Ruby, or why would you
         | prefer it overall over TS ?
         | 
         | When I was playing around with ruby for any significant sized
         | projects, I found it became unmaintainable. Granted I was most
         | definitely using it wrong, but apart from that, I didn't see
         | the appeal.
        
           | egonschiele wrote:
           | One reason is blocks. I wish other languages had blocks.
           | Swift has them, and it means you can write some very readable
           | DSLs. Tbh I'm happy with Typescript, but Ruby was my language
           | of choice for years.
        
           | izietto wrote:
           | My two cents:
           | 
           | 1. 2. 3. 4. 5. standard library
           | 
           | 6. consistency with OOP *
           | 
           | 7. "everything is English language". I find good Ruby
           | readable just like books **
           | 
           | * Ruby is the best translation of "everything is an object"
           | imho
           | 
           | ** evil Ruby is the Perl side of the moon, but it's easy to
           | ignore it
           | 
           | > When I was playing around with ruby for any significant
           | sized projects, I found it became unmaintainable
           | 
           | Ruby requires tons of discipline, as it has obscure meta-
           | programming powers that are meant to be used for DSL
           | libraries, while usually Ruby beginners spam them
           | understating their make your codebase unmaintainable.
        
             | stouset wrote:
             | Agreed. Ruby is the epitome of "with great power comes
             | great responsibility".
             | 
             | I have inarguably written some of the absolutely most
             | elegant solutions of my entire career in it. But without
             | good discipline, experience, and understanding you can
             | absolutely make a horrifying mess of unrivaled proportion.
             | 
             | Unfortunately when you work on real world projects where
             | most devs are relatively junior (or otherwise inexperienced
             | with Ruby) or on projects where there have been multiple
             | cooks who didn't necessarily share the same underlying
             | mindset about the project, you end up with more of the
             | latter than the former.
             | 
             | But for small, consistent teams of experienced Ruby
             | engineers? It can be incredible.
        
           | ncphillips wrote:
           | I used to hate Ruby.
           | 
           | Last year I spent 10 months alone on a Rails app. I decided
           | to buy in. Do things the Ruby/Rails way. Read books on how to
           | think about OO in Ruby. TDD everything.
           | 
           | I can't quite say what changed. But now Ruby is my favourite
           | language.
           | 
           | It is almost encourages you to write clean code but it
           | doesn't force you to. It lets you make mistakes. It treats
           | you like a grown up.
           | 
           | TS, Java, etc. They treat you like a child who can't be
           | trusted to do things right.
           | 
           | Yes, this means you can end up with some horrifying god awful
           | Ruby code. But it also means you can end up with some truly
           | beautiful abstractions.
           | 
           | I think the very things that prevent you from creating
           | unmaintainable messes are the things that prevent you from
           | writing incredible pieces of software.
           | 
           | That's probably why they miss Ruby
        
             | jjnoakes wrote:
             | I don't mean to pick on your comment specifically, but I
             | really dislike the notion that someone who appreciates,
             | prefers, or relies on static types and a more strict
             | compiler or toolchain is somehow not a grown-up.
             | 
             | [Edit]: Added a missing word
        
               | stouset wrote:
               | I don't know that that's GP's assertion at all.
               | 
               | I feel similarly re: Java and Ruby. But I also love Rust
               | which relies on static types and one of the strictest
               | compilers in existence.
               | 
               | But there's something I can't quite put my finger on
               | where--yeah--Java and Golang (in my mind) force me into a
               | boxed-in world where I'm not trusted to make good
               | decisions about abstractions, but Rust and Ruby encourage
               | me to do so.
        
         | parthdesai wrote:
         | https://sorbet.org/ ?
        
           | egonschiele wrote:
           | So many JS projects are switching to TS, but AFAIK the same
           | isn't happening within Ruby, which reduces some of the type-
           | checking benefit.
           | 
           | Also, this is subjective but I don't like the syntax.
           | 
           | > Sorbet is 100% compatible with Ruby. It type checks normal
           | method definitions, and introduces backwards-compatible
           | syntax for method signatures.
           | 
           | I would have preferred if they had introduced a compile step
           | the way typescript does, and provided a TS-like syntax. I
           | find the current version hard to read.
        
             | whakim wrote:
             | The problem (as TFA points out) is that most of the
             | available options (including "a TS-like syntax") are
             | already valid Ruby code.
             | 
             | I do agree with your first point that the lack of adoption
             | of Sorbet among library maintainers negates some of the
             | benefit of the type system (compared to TS). Seeing all
             | those `T.untyped`'s in generated RBI files is a little
             | scary :D.
        
             | bilekas wrote:
             | > So many JS projects are switching to TS, but AFAIK the
             | same isn't happening within Ruby
             | 
             | The sheer propagation of JS might have something to do with
             | the big push to have some kind of typing. From my own
             | experience, if I see ruby I know I can either re-write it
             | or find an alternative in TS/JS.
             | 
             | The ubiquity of JS makes it more accessible, but I'm still
             | trying to find reasons why one would choose Ruby.. I'm
             | always a 'right tool for the job' but I don't know what the
             | niche is.
             | 
             | Edit : My pedant in me :
             | 
             | > compile step the way typescript doe
             | 
             | Typescript transpiles to JS. I don't 'believe' there is a
             | compilation step.
        
               | Stratoscope wrote:
               | > > compile step the way typescript [does]
               | 
               | > Typescript transpiles to JS. I don't 'believe' there is
               | a compilation step.
               | 
               | This is a common misconception. Transpiling is not
               | something distinct from compiling. "Transpiler" is just a
               | trendy name for a certain subset of compilers. Just
               | because it compiles to another "high level" language
               | doesn't mean it's not a compiler.
               | 
               | Every "transpiler" _is_ a compiler.
               | 
               | Sources:
               | 
               | On BIX in the 1980s, when the only implementation of C++
               | was Cfront, which translated to C, I asked Bjarne
               | Stroustrup if it was a preprocessor. He told me quite
               | emphatically, "No, Cfront is a _compiler_. " (I don't
               | think the term "transpiler" was in common use at that
               | time.)
               | 
               | The Wikipedia article on Cfront agrees:
               | 
               | > _Cfront was the original compiler for C++ (then known
               | as "C with Classes") from around 1983, which converted
               | C++ to C_
               | 
               | https://en.wikipedia.org/wiki/Cfront
               | 
               | More recently, and relevant to this discussion, the
               | TypeScript team specifically calls _tsc_ a compiler:
               | 
               | > _Let's get acquainted with our new friend tsc, the
               | TypeScript compiler._
               | 
               | https://www.typescriptlang.org/docs/handbook/2/basic-
               | types.h...
               | 
               | In fact, if you search that page for "pil", you will find
               | nine references to "compile" and none for "transpile".
        
             | nine_k wrote:
             | TS has a way to attach type annotations (.d.ts files) on
             | top of existing JS code base, thus allowing for gradual
             | migration and relatively peaceful coexistence. Same with
             | Python: you can add type definitions on top of existing
             | untyped code, as a separate package. In either case, types
             | can be provided by a third party, e.g. yourself if you want
             | to use a particular library and it still lacks typing
             | support.
             | 
             | Does Sorbet offer something comparable? That would make
             | adoption easier.
        
               | dragonwriter wrote:
               | > Does Sorbet offer something comparable?
               | 
               | Yes, RBI files.
        
       | ezekg wrote:
       | As a huge proponent of Ruby's new pattern matching syntax (it has
       | changed the way I write Ruby), I really, _really_ like the idea
       | of defp. So many times I wish I could overload a method,
       | dispatching by pattern. Instead, I have to pattern match in the
       | method body, which causes the method definition to become
       | convoluted:                   def for_environment(environment)
       | environment =             case environment             in String
       | => code unless code in UUID_RE               return none # no
       | support for filtering via environment codes             in
       | UUID_RE => id               return none unless env =
       | Environment.find_by(id:)                    env             in
       | Environment => env               env             in nil
       | nil             end                      ...         end
       | 
       | I think defp looks and _feels_ very Ruby-like compared to the
       | alternatives (which have the problems you discussed).
       | 
       | And Elixir is great, so I can appreciate the inspiration.
        
         | [deleted]
        
         | [deleted]
        
       ___________________________________________________________________
       (page generated 2023-05-05 23:00 UTC)