[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)