Live data from Hacker News

Ruby 4.0.0

ruby-lang.org

31–40 of 197 posts

Re: Ruby 4.0.0

#31
post #6

It seems Ractor is still work in progress while Fiber has matured a lot in the last few releases. I vaguely remember reading Shopify is using Fiber / Rack / Async in their codebase. I am wondering if Rails will get more Fiber usage by default.

To me it seems very few people use ractors. A bit more use fibers though. It's a bit of a mess IMO. I'd much prefer everything be simplified aggressively in regards to threads + GIL; and Ractors integrated on top of Ruby::Box to provide not only namespaced container-like entities but also thread-support as a first-class citizen at all times. The API of ractors is weird and really not fun to use.

I really enjoyed using them for my Ruby file-matching library where I wanted to read `shared-mime-info` XML source package files directly and on the fly as opposed to using the pre-processed secondary files that the upstream `update-mime-database` tool spits out. The problem is that a type definition can be spread out over multiple XML packages in both system and user paths, so the naïve implementation of reading them all at once wastes a massive amount of memory and a massive number of object allocations (slow) when most people use maybe 5% of the full set of supported types (the JPEGs and HTMLs and ZIPs of the world).

I wanted to read the source package files directly because I always found `shared-mime-info`'s usual two-step process for adding or editing any of the XML type data to be annoyingly difficult and fragile. One must run `update-mime-database` to decompose arbitrarily-many XML packages into a set of secondary files, one all-file-extensions, one all-magic-sequences, one all-aliases, etc. System package managers usually script that step when installing software that come with their own type data. I've accidentally nuked my entire MATE session with `update-mime-database` before when I wanted to pick up a manual addition and regenerated the secondary files while accidentally excluding the system path that had most of the data.

I ended up doing it with four Ractors:

- a Ractor matching inputs (MIME Type strings, file extensions, String or Pathname or URL paths for sniffing) against its loaded fully-formed type definition objects.

- a Ractor for parsing MIME Type strings (e.g. "application/xml") into Hash-keying Structs, a task for which the raw String is unsuitable since it may be overloaded with extra syntax like "+encoding_name" or fragment ";key=value" pairs.

- a fast XML-parser Ractor that takes in the key Structs (multiple at once to minimize necessary number of passes) and figures out whether or not any of those types are defined at all, and if so in which XML packages.

- a slow XML-parser Ractor that takes the same set of multiple key Structs and loads their full definition into a complete type object, then passes the loaded objects back to the matcher Ractor.

The cool part of doing it this way is that it frees up the matcher Ractor to continue servicing other callers off its already-loaded data when it gets a request for a novel type and needs to have its loader Ractors do their comparatively-slow work. The matcher sets the unmatched inputs aside until the loaders get back to it with either a loaded type object or `nil` for each key Struct, and it remembers `nil`s for a while to avoid having to re-run the loading process for inputs that would be a waste of time.

The last pre-Ractorized version allocated around 200k objects in 7MiB memory and retained 17k objects in 2MiB of memory for a benchmark run on a single input, with a complete data load. The Ractorized version was twice as fast in the same synthetic benchmark and allocated 20k objects in 2MiB of memory and retained 2.5k objects in 260KiB of memory for its initial minimal data load. I have it explicitly load `application/xml` and `application/zip` since those combined are the parent types for like a third of all the other types, and a few other very common types of my choosing.

I think a lot of the barrier to entry for Ractors isn't the API for the Ractors themselves but in figuring out how to interact with Ractorized code from code that hasn't been explicitly Ractorized (i.e. is running in the invisible “main” Ractor). To that end I found it easiest to emulate my traditional library API by providing synchronous entry-point methods that make it feel no different to use than any other library despite all the stuff that goes on behind the scenes. The entry methods compose a message to the matcher Ractor then block waiting for a result or a timeout.

I also use Ractors in a more lightweight way in my UUID/GUID library where there's a Ractor serving the incrementing sequence value that serves as a disambiguator for time-based UUIDs in case multiple other Ractors (including invisible “main”) generate two UUIDs with the same timestamp. Speaking of which, I'm going to have to work on this one for Ruby 4.0, because it uses the removed `Ractor.take` method.

Re: Ruby 4.0.0

#32
Glad to see internal stack traces cleaned up (maybe we can get relative paths some day?) and Set finally get the respect it deserves!

Re: Ruby 4.0.0

#34
post #28

Have they improved tooling? I've yet to get any lsp working on windows

IMO programming on windows is just asking for punishment. Unless it’s a Microsoft language, you’re so much better off on Linux or macOS.

Re: Ruby 4.0.0

#35

Ruby is amazing. I recently built a layer on top of Rails that can generate an API from a single markdown file. I did the same thing in python but it was much harder and JavaScript would have been a beast. Ruby can meta program like nothing else.

Curious to hear more about this, do you have any examples?

Re: Ruby 4.0.0

#36
post #26

Earlier quoted context omitted.

It's literally faster than Python but ok.

Is being faster than Python considered to be a notable feature?

Personally I don't care about speed for this category of language. I just bring it up because Python is one of the most used languages, is even slower, yet that's never held against it. Just seems like a lazy way to dismiss Ruby. Yeah, it's not as fast as C, Go, Rust or Java. Everyone knows and raw speed obviously isn't the point of a dynamic scripting language...

Re: Ruby 4.0.0

#37
post #28

Have they improved tooling? I've yet to get any lsp working on windows

IMO programming on windows is just asking for punishment. Unless it’s a Microsoft language, you’re so much better off on Linux or macOS.

Or wsl2

Re: Ruby 4.0.0

#38
post #19
post #12

It's never Christmas without a new ruby version. The ruby::box thing looks pretty interesting, from a cursory glance you can run two simultaneous versions of something like a feature or rollout much more conveniently. Also being able to do if condition1 && condition2 ... end on multiple lines rather than one - this is pretty nifty too!

I've been doing if condition1 && condition2 ... end for ages and it seems to work find, what am I missing with this new syntax?!

In languages where placement don't matter, like c/js, I prefer leading booleans. It makes it much easier to see the logic, especially with layers of booleans.

Re: Ruby 4.0.0

#39
post #19
post #12

It's never Christmas without a new ruby version. The ruby::box thing looks pretty interesting, from a cursory glance you can run two simultaneous versions of something like a feature or rollout much more conveniently. Also being able to do if condition1 && condition2 ... end on multiple lines rather than one - this is pretty nifty too!

I've been doing if condition1 && condition2 ... end for ages and it seems to work find, what am I missing with this new syntax?!

I prefer the new way because if you want to remove one condition you just delete the line rather than having to edit in multiple places.

Re: Ruby 4.0.0

#40

I haven't looked at Ruby for a long time. I've moved away due to the lack of typing. Any degree of typing would be helpful. Does it support typing yet?

There is [RBS]( https://sorbet.org/ ) (part of ruby 3) and [sorbet]( https://sorbet.org/ ). To be honest, these aren't widely used as far as I am aware. I don't know if it is runtime overhead, ergonomics, lack of type checking interest in the ruby community or something else. Type enforcement isn't a big part of ruby, and doesn't seem to be gaining much momentum.

I’ve been leaning hard into Sorbet runtime types for DSPy.rb[0] and finding real value. T::Struct at API boundaries, typed props for config, runtime validation where data enters the system.

For generating (with LLMs) API clients and CLIs it’s especially useful—define the shape once, get validation at ingress/egress for free.

Maybe momentum is happening in new projects rather than retrofits? [0] https://oss.vicente.services/dspy.rb

Post reply on HN