Ruby already solved my problem
newsletter.masilotti.com259 points by joemasilotti 3 days ago
259 points by joemasilotti 3 days ago
If you ignore performance and mathematical elegance and safety and just look at how much a language lets you get away with from a productivity standpoint, I think Ruby is a pretty standout winner and nobody else even comes close really...
Very clear APIs and syntax(with the possible exception of blocks which can be weird because they aren't quite functions), and tons of raw metaprogramming powers.
You can argue it sacrifices too much of the other things to deliver on these things, but it's hard to argue against it doing well at what it optimizes for!
Let's be honest, when you're starting a startup, Ruby's performance won't be a bottleneck until much later, when you're successful and get tons of usage - at that point you can afford to hire someone to fix it. Your productivity will be a bottleneck from the very beginning.
Pieter Levels writes his startups in PHP and hasn't had a performance bottleneck so far. For most applications, the performance of the language won't be an issue. I personally wouldn't pick PHP for any of my own projects, but Ruby I'd pick any day.
Ruby’s performance has not been a bottleneck for multiple companies that have IPO’d and are worth billions of dollars.
> at that point you can afford to hire someone to fix it
That's the reason why we can't have nice things.
I love writing Ruby. It’s one of the most pleasant and elegant languages I’ve used… but the footguns it comes equipped with rival those of Perl.
> I love writing Ruby
I work at an enterprise running Rails (you've heard of us, if you're in North America). Discussions about rails abound, and my opinion has distilled into "I love writing Ruby, I loathe reading it"
Same! I had a job at a shop with a monolithic Rails app where I had so much trouble understanding the codebase I almost quit the industry entirely.
Maybe I am just stupid when it comes to reading Ruby/Rails or maybe that codebase was uniquely awful, but it was ~impossible to figure out where things were defined or how data moved through the system. A huge ball of mutable state that was defined at runtime.
When people say "I love writing Ruby" what I hear is "I love writing greenfield Ruby". Everybody loves writing greenfield code! The difference between greenfield and brownfield Ruby is stark, in my experience.
And to be clear I do not hate Ruby. It got me into the industry, it taught me a lot, it just optimizes for a set of values that I don't happen to share anymore, which is fine.
A lot of Ruby's syntax lends itself towards cramming as much business logic on the screen as it can. I used to say it's a "semantically dense" language, but I don't actually know if that's technically accurate. Compared to Java or Rust I certainly felt like Ruby fit more "logic" into a screenful of code, but at the cost of any other context (type annotations, import notes, etc)
I strongly appreciate how much decision fatigue Rails avoids by just offering you so much batteries-included stuff. I tried getting into Django and immediately spun out fretting over what ORM or migration manager or caching system to use. (One of my coworkers who is a huge Django says I'm nuts and that Django offers those things too, so I may be misremembering.) Rails being as opinionated as it is saves so much thinking effort along those lines.
I think both of those facets make it extremely appealing to, as you say, anyone greenfielding code, and are the exact things that make it an absolute trash fire to maintain anything of appreciable size.
We're constantly fielding incidents due to something being undefined or unexpected nils or other basic typecheck failures.
> it just optimizes for a set of values that I don't happen to share anymore
That is a lovely way to put it, I'm gonna steal that
Agreed. Also pretty to look at IMHO
But awful to navigate - the terse syntax combined with lack of static types and regular use of generated identifiers turns large codebases into Where's Wally. Good luck finding where the `process` function is called from. You can't even search for `process(` like you can in most languages.
You can search for "def process" - it's inheritance and metaprogramming that make finding the underlying implementation difficult.
>> Good luck finding where the `process` function is called from. You can't even search for `process(` like you can in most languages.
> You can search for "def process"
That tells you where it is defined, not where it is called from.
Well, just add "puts caller" in the function to find out. You can do this in your own code, but also you can also just briefly patch the library you're working with, if that's where the process is.
By the way, the generated identifiers are more a rails thing than a ruby thing.
Doesn't that just tell you the functions that happen to call it when you run a program? That's not remotely as good as just getting a complete list at the click of a button.
> lack of static types
RBS is a thing; we use it extensively.
> Good luck finding where the `process` function is called from
I don't use it like that but I seem to recall RBS itself has a query mechanism and can answer these kind of questions.
> we use it extensively
Who is "we"? The Ruby projects I use are Asciidoctor and Gitlab and neither of them use it.
Also putting types in a totally separate file is an abysmal UX.
> Who is "we"?
$dayjob; does it really matter who as long as it's an example of it being useful to someone?
> putting types in a totally separate file is an abysmal UX.
Is it though? I much prefer
def frobz(fizbazbang)
# ... the code
to def frobz(Hash<Symbol | String | Integer, Array<String | Integer>> fizbazbang) -> Array<Hash<String | Integer, String>> fizbazbang
# ... the code
It's the job of the code editor to get that information surfaced to me at the proper place (e.g virtual content like vim inlay hints, hover tooltips, those vscode embeds or whatever they are called). If I need to jump quickly I have projections† set up.Having the types inline isn't that useful anyway as it gives yo only the signature but gives you no info of whatever type intermediate things are without much thinking.
And interesting things happen when you have separate `.rbs` files: I started to develop some things type first then progressively fill the implementation in.
† Well known because of https://github.com/tpope/vim-projectionist but projections are not limited to vim.
> if you ignore performance
man, people are still parroting decade old, incorrect talking points I see.
Is ruby as performant as C, probably not, although, actually, in some cases, it outperforms C -> https://railsatscale.com/2023-08-29-ruby-outperforms-c/
One of the largest ecommerce apps in the world runs ruby, shopify. Ruby now has a JIT, there has been insane effort put into making ruby faster.
> actually, in some cases, it outperforms C -> https://railsatscale.com/2023-08-29-ruby-outperforms-c/
Bit of a clarification after reading the article - that article demonstrates a pure-Ruby implementation [0] outperforming a C extension [1], which is not what I had originally expected when first clicking on the link.
[0]: https://github.com/tenderlove/tinygql
[1]: https://github.com/rmosolgo/graphql-ruby/tree/master/graphql...
Various Lisps can give it a run for its money, depending on the problem.
Metaprogramming is Lisp's canonical super power. Ruby is going to win out on tasks where it has built in syntax, like matching regular expressions.
But once you get to metaprogramming Lisp macros are going to give Ruby a run for its money.
I will say one of the under appreciated aspects of Ruby is the consistency of its semantics, where everything is message passing very much like Smalltalk.
I am extremely partial to Scheme’s `define-syntax` construct. I remember the first I saw it, I thought it was one of the elegant and amazing things I had ever seen in a programming language, and I kind of got annoyed that it wasn’t something easily available in every language.
I love me some Clojure, and its macros aren’t bad or anything, but I feel Scheme (and Racket) has the most elegant metaprogramming.
What about PHP/Symfony? I have more experience in that and, after trying rails out this week, so much was overlapping.
Does Base https://github.com/garybernhardt/base still work with current versions?
"If you ignore performance and safety..."
Other than that, how was the play, Mrs. Lincoln?
Also, add readability and maintainability to that list, and scaling to a large codebase. And good debugger support.
Right. But ruby also has awful crap. The documentation - look at opal, webassembly and many other projects in ruby. The documentation is just total garbage.
rubygems.org also has decided to, rather than fix on existing problems, eliminate all former maintainers and instead put in Hiroshi Shibata as the solo lead - the same guy who keeps on writing on different github issue trackers how he does not have time to handle any issue requests for low-used projects. Wowsers.
Ruby has a lot of these hidden gems (pun intended).
I wouldn't be as much in love with programming, if it wasn't for Ruby. And although I use many other programming languages these days, Ruby will forever have a special place in my heart.
A long while back I wrote a bunch of articles covering some of the standard library: https://snakeshands.com/series/ruby_standard_library/
Ruby, and Ruby on Rails is a treasure trove of little handy bits you can use if you just know where to look. I really miss some aspects of ruby (I just don't have a chance to use it these days).
I'm worried LLMs will make people ignore what's already there and auto generate useless functions instead of using what's there in Ruby/Rails. I've been using Rails for almost 20yrs (on and off) and I can't count the number of times I did something only to find out it was either natively supported in a recent version... or at least a new best practice in modern Rails.
You find the same thing with JS to an even higher degree, but there's always 10 options in NPM and they all need to be updated every year otherwise the other 20+ packages you depend on can't be upgraded. There's a stark contrast in maintenance overhead and DX between frontend and server side.
Even the rails JS libraries are very stable. Hotwire's Stimulus hasn't had a release since 2023 and it always works just fine. https://github.com/hotwired/stimulus/releases
> I'm worried LLMs will make people ignore what's already there and auto generate useless functions instead of using what's there in Ruby/Rails.
I think you're probably right. But fwiw, as a non-Rubyist who values good style and is recently working in a one-off Ruby codebase, I've found it easy to use LLMs to write Ruby code that is idiomatic and leverages built-ins well. I use ChatGPT 5 Thinking for this, and I don't let it generate any code that I use directly. I ask it about ways to do things, including stuff built-in to the stdlib, and sometimes have it generate 3 or 4 implementations. Then I compare them, consult the language docs or stdlib API docs or the pickaxe book, and choose what seems the most stylish or composable. I then write it out by hand, bearing in mind what I just learned, and see what Rubocop has to say about its style.
I wouldn't say LLMs have been essential in this, but they've been pretty convenient. Because Ruby has relatively a lot of special syntax, it's also nice to be able to inquire directly about the meaning of some syntax in a bit of generated code-- especially when it involves characters that are (for some reason) ungoogleable, like an ampersand.
I think people who use LLMs to generate code and people that embrace agentic coding AIs and "vibe coding" will absolutely fall into the pattern you describe. But RTFMers and developers who care about craftsmanship will probably use LLMs as another discovery mechanisms for the stdlib, popular Gems, and popular style conventions.
Agreed. Was looking around for STL files so I could print a ruby and put it on my desk.
Glad to see it's getting love on here recently.
I gave it a try! I tried to get the odd-ball perfectly circular cut and square dimensions, but I'm mostly just eyeballing it. Haven't tried printing it yet, but I have some nice red filament that I think is going to look good!
Here's a screenshot from inside FreeCAD:
https://f.toi.sh/rubygem-screenshot.png
A nice manifold solid:
https://f.toi.sh/rubygem.3mf / https://f.toi.sh/rubygem.stl
A terrifying non-manifold FreeCAD mess: (requires surface WB)
I love this idea!! Any luck finding an STL or at least a 3d model I can convert and copy your idea?
A quick search returned this: https://sketchfab.com/3d-models/gemstone-pack-68c4ec3dd23247...
Not _exactly_ the same cut, but might be good enough for you?
Unrelated side note, but I haven't written any Ruby in maybe 15 years or so and dammn I forgot how elegant the language is at its core. The author's AppVersion class is so nicely done, it's nuts how succinct eg the compare implementation is.
Having done mostly TypeScript and Elixir lately, I had forgotten things could be so succinct yet so clear. The combo of modern (to me) Ruby's lambda syntax (in the .map call), parentheses-less function calls, the fact that arrays implement <=> by comparing each item in order, that there's an overloadable compare operator at all, having multiple value assignments in one go... It all really adds up!
In any other language I can think of real quick (TS, Elixir, C#, Python, PHP, Go) a fair number of these parts would be substantially more wordy or syntaxy at little immediately obvious benefit. Like, this class is super concise but it doesn't trade away any readability at all.
Having learned Ruby before Rails became commonplace, with its love for things that automagically work (until they don't), I had kinda grown to dislike it. But had forgotten how core Ruby is just an excellent programming language, regardless of what I think of the Rails ecosystem.
Ruby trades away quite a few things for readability. It's beautiful but a lot is being hidden.
Some of those languages would have you deal with the problem of allocating multiple arrays in the heap just to compare three numbers. Or give you tools to outlaw passing invalid strings to AppVersion.new (quick: what is the comparison between AppVersions "foo" and "bar"?).
Plus you have very few tools to ensure code remains beautiful. I've worked with Ruby for close to two decades, almost nothing in the real world looks that clean. Take a look at the Gem::Version#<=> implementation that the article talks about: https://github.com/ruby/ruby/blob/master/lib/rubygems/versio...
Wow I wonder why it's so verbose. Performance optimizations? Seems like this wouldn't be called often enough to show up in any performance profiling exercise.
Ruby is very slow so you gotta squeeze everything you can everywhere. Even a seemingly simple method will have to be scrutinized so that overall performance isn't impacted. It's death by a thousand cuts.
See the commit that made it complex: https://github.com/ruby/ruby/commit/9b49ba5a68486e42afd83db4...
It claims 20-50% speedups in some cases.
There's churn that comes with that. Ruby will have code that is ever changing to gain 5%, 10% performance every now and then. You gotta put that on balance: in a language like Go this method would've been ugly from the start but no one would've needed to touch it in 100 years.
You never gotta squeeze everything you can everywhere!
Regardless of how slow the language is, the 90/10 rule applies: 90% of the time is spent in 10% of the code. Optimize that 10%! Making the rest of the code faster isn't worth the code quality cost.
That's a rule that might hold for applications and services. It does not hold for languages and libraries, where any and every aspect is going to be the bottleneck in someone else's code. It's a different 10% for each user.
You can’t build a Ford Pinto and then swap the piston heads and rear differential to get a Formula 1 car.
There are plenty of businesses that have under 10k users and can live perfectly well with http requests around 500-1000 ms. When there are performance issues, 95% of the times they come from the database, not the language.
I somewhat agree. In general most apps are small where the language choice doesn’t really matter.
Caching is also vastly underutilized, most apps are read-heavy and could serve a significant portion of their requests from some form of caching.
> When there are performance issues, 95% of the times they come from the database, not the language.
Eh, statements like these are always too hand wavy. Resource usage has to do with performance, the DB has no fault in it but the runtime does.
Having worked with Rails a ton there’s a very large overhead. Most apps would see a significant speed up if rewritten in a faster language and framework, with no changes to the DB whatsoever. The amount of memory and CPU expended to the app servers is always significant, often outweighing the DB.
But what do you mean, give me a real example. You loaded too many active_records in memory and it's using a ton of ram? Did you try pluck, batches or even skipping active_record and using a raw query?
Unless you really need to scale for a ton of users, you don't have to go crazy to get decent performances out of rails/ruby. How many requests/sec are we even talking about here?
> When there are performance issues, 95% of the times they come from the database, not the language.
DHH used to say that it didn't matter if Rails was slow because the database was I/O bound anyway. But that was a long time ago. Times have changed considerably. Most especially because DHH now promotes using SQLite, which completely takes the worst I/O offender right out of the picture. Nowadays the language (when it is as slow as Ruby) is most likely to be the problem.
Doesn't matter if SQLite is now viable with rails, no legacy rails app is using it and it's not like you're going to wake up one morning and migrate your production db from postgres, mysql, sql server or oracle just because you felt like it.
In theory the language is slow, in practice it doesn't really matter because the db is much slower unless you're github or twitter and you really need to scale.
When you choose ruby, you trade faster dev time for slower runtime. I am OK with this trade-off 99% of the time. My dev time costs way more than a couple ms lost on my production server.
> When you choose ruby, you trade faster dev time for slower runtime.
Ruby is a beautiful language, but that does not translate to efficient use of dev time. Ruby is not a language that you can quickly write usable code in. Some other languages are clearly more productive. You can create works of art, though, which is still pretty appealing. Ruby does have a place in this world.
It was, again, DHH/Rails that used to make the claim about developer time — premised on Rails eliminating all the so-called "situps" that other frameworks imposed on developers. It is very true that when you eliminate a bunch of steps you can move a lot faster. But:
1. Everyone and their bother have created Rails clones in other languages that also eliminate the same "situps", negating any uniqueness Rails once offered on that front.
2. It turns out those "situps", while a dog in early development, actually speed development up down the road. If you are churning out a prototype to demonstrate an idea that will be thrown away after, its a pretty good tradeoff to ignore what you can, but things become far less clear cut when a program finds longevity.
> Nowadays the language (when it is as slow as Ruby) is most likely to be the problem.
These days the main issue why web apps are slow or fragile is because they are huge React apps that do way too much in the browser than they need too. The response time from the server is rarely the issue unless you're doing heavy analytics. High end React shops do some crazy optimization to try to compensate for this fact. Linear is the only one I've seen do it well with JS.
>> When there are performance issues, 95% of the times they come from the database, not the language.
> DHH used to say that it didn't matter if Rails was slow because the database was I/O bound anyway. But that was a long time ago. ... Nowadays the language (when it is as slow as Ruby) is most likely to be the problem.
Nowadays CPU speeds, available RAM, and network speeds dwarf what was top-of-the-line "a long time ago," making the trope of "Ruby is too slow" much less believable "nowadays."
"Too slow" is a mischaracterization. Ruby was never too slow, only comparatively slow. What DHH was talking about is that when the system was I/O bound, even if you could eliminate all the time spent in Ruby, you'd only see small percentage increases in performance at best. But the calculus has changed. I/O isn't the bottleneck like it was when those statements were made. Now, if you could eliminate the time spent in Ruby, you'd see significant percentage increases in performance.
You previously stated:
Nowadays the language (when it is as slow as Ruby)
is most likely to be the problem.
And now state: "Too slow" is a mischaracterization. Ruby was never
too slow, only comparatively slow.
In response to my identifying the "Ruby is too slow" trope. Furthermore, when you assert: Now, if you could eliminate the time spent in Ruby,
you'd see significant percentage increases in performance.
This implies a performance issue within Ruby when, in fact, the narrative has now been shifted into execution percentage allocations. For example, if an overall execution flow takes 1 millisecond and time within the Ruby runtime accounts for 600 microseconds, then the above would be true.One way to describe your position is disingenuous.