Back when autocompletion and stuff were only available in Visual Studio/Xcode/Other bug IDEs, I was forced to use Ruby and fell in love with it. It didn't matter what I used as my editor was Sublime. But when VSCode came and language features became democratized, I never touched a type-less language again. Why should someone opt for a language with absolutely no features where one can have autocompletion, typechecking, deep data type exploration, jumping to definitions and implementations? I really think it's a bad choice of Ruby not to care for types. And well we now have Crystal which again makes me question why Ruby? And it’s a shame no language is as beautiful as Ruby, not in features choices, design elegance, balance, beauty of the syntax, joy of programming mindset, not even in the name and logo. I wished Matz rethinked this part.
I use RBS/steep with great success to catch plenty of nil issues early, but it's similarly not great from a dev POV to have to maintain a completely separate set of rbs files (or special comments with rbs-inline). Also in my experience, modern editors don't leverage it for typing/intellisense.
Sorbet now supports inline RBS signatures, which I find a lot more readable. If you use VS code with Ruby LSP, the syntax highlighting is pretty great for the signatures too.
Based on what Ive read [1], Sorbet was born out of very specific circumstances at Stripe, in the same sort of way that go was at google. I think it missed the mark due to it being a specific tool for specific usecases instead of an "open source project for the general public" first.
I love ruby but I do look forward to having a good option for types at some point. The community makes up for lack of types by adding more tests to their test suite, which is nice but I'd love to layer some encoding into the codebase itself and save the CI time.
Static type-signatures are inevitable in general. You can see this by how even the Ruby documentation has to make up silly ad hoc notation like "→ array_of_strings", "→ matchdata or nil" etc.
I think Ruby is really great at what it tries to do. The only "problem" with it is that python kind of sucked the air out of the room and ruby got shoved into a little niche.
These languages are really quite similar in many ways, but their domains ended up diverging. With python becoming the layman / scientific / learning language of choice, ruby has been pigeon holed into mostly web development.
Both are really easy to pick up and learn for somebody unfamiliar with CS concepts and I personally find the ruby syntax far more intuitive.
We have a lot more options now. For a while people tried to use python and ruby as glue / systems programming languages, but with golang and rust you have really good and more performant options in that space. And as you say the tooling has improved massively, so the hurdle of moving on to a more "rigid" language is less than it ever was.
I still really like ruby, and I think rails is still a powerhouse due to solving so many real world problems in a really complete package, but the lack of adoption outside of that niche has left it dwindling in popularity.
As someone coming from Ruby to TypeScript, I find types cumbersome, verbose, complex, and not of much use. I have been writing and reading TS for the past six months. What am I missing?
I have been building web apps for long enough to remember when it was commonplace to SSH (or Telnet!) into the server and just vim index.php. As you can imagine, it was pretty easy to bring the site down like that, so we started doing what is now called Continuous Integration and Continuous Deployment: automated tests as a precondition for deployment.
Later, we adopted static analysis tools and code linters. This helped, but only so much could be done with the dynamically typed languages which were popular in the early internet.
As a self-taught programmer with no college degree, I started with dynamic languages: Perl, JavaScript, PHP, Ruby. I viewed the statically typed languages (and the programmers who could wield them) with reverence and awe. C, C++, Java, these were “real” languages that required big brains, and I should stay in my dynamically typed playpen. But as I matured, my understanding shifted: dynamic languages require hubris, require belief that you can keep an entire complicated codebase in your head with perfect recall. Statically typed languages, it turns out, invite humility.
With a static type system, each step in a program’s lifecycle can be a little contract with itself. Break that contract and the language will tell you exactly how and where. These languages can provide immediate feedback to the developer about a program’s correctness before it is even run. Bridging the gap between all of the existing dynamic code (and the developers who wrote it) and the statically typed utopia lies so-called gradual type systems such as TypeScript.
I like to use a meteorological analogy here: if JavaScript is “wintry mix,” then TypeScript lowers the temperature just enough to build a snowman.
Typescript is a bad introduction to the world of static types in a lot of ways. Typescript is incredible for what it is doing, but still, at its core, laying down a static type system on top of a language ecosystem that was dynamic for years is fundamentally just, well, a bit whacky compared to a language ecosystem that was static from the beginning.
I've been doing Typescript now for about 9 months, so I wouldn't call myself an expert in that, but I also have decades of experience in many other dynamic and static languages (including Haskell), so I can say with confidence that a lot of features in Typescript are to solve problems unique to the Javascript world, and then there are the features in Typescript that are there to solve problems in the other Typescript solutions, and all-in-all while it has great utility and has many fantastic features it just isn't possible to completely overcome the fact that it's a static type system, on top of a dynamic type system.
I've seen a number of posts to the effect of "I'm coming from Typescript and learning Go, how do I do X" and so often the answer has been "Even in Go, even with its very simple type system by static language standards, the answer is that you don't do X at all because X is a feature on top of a feature on top of a feature designed to deal with being on a dynamic language and when you're on a static language you not only don't deploy that solution, you wouldn't even have the problem that's the solution to except that you just created it for yourself by copying Typescript too closely in some other way." A simple example being you don't ask how to type a map/dict/etc. in a normal static language based on the contents of the "type" value, you just declare a properly-typed value in the first place and always use that, at most converting at the edge.
Typescript is a great tool but a very unrepresentative view on how static typing works.
Types really start pulling their own weight as the size of an application increases.
In order to catch problems in dynamic type languages you end up needing a bunch of additional tests to, ironically, verify the expected type. And even then, those tests don't and can't tell you how a method is actually used throughout the program.
Consider the following class
class Human
def initialize(name)
@name = name
end
end
Now imagine you want to refactor this class to something like this
class Human
def initialize(first, middle, last)
@fullName = "#{first} #{middle} #{last}"
@first = first
@middle = middle
@last = last
end
end
All the sudden you've got a problem on your hands. You have to find everywhere that referenced `name` on a `Human` object (And don't mess that up, it could be `name` on the `Pet` object) and change them over to full name, or figure out if they are doing something tricky like trying to extract the first name from `name`.
Types make refactoring this sort of code somewhat trivial. You simply change the fields and let the compiler tell you ever position which relied on that field.
This extends into all sort of circumstances, like letting the user of a method know what types are expected. For example, the above ruby code doesn't really guarantee that `first` is a string. In fact, it could be an int or even an object and the code would likely handle it just fine. Types expose interface contracts in a quick and easy way for programmers to understand what they are dealing with.
The classical counterargument to this is that, if you have good test coverage then eliminating @name should lead you directly (via failing tests) to where the name field was being used. This works especially well in codebases which enforce what I tend to call "pseudo"-type systems, for example via clojure's spec and Racket's contracts. Where the shape of data is enforced structurally at runtime.
To be honest, the argument in favor of static typing that I find more compelling is the IDE argument. It definitely is a much richer experience browsing code in an IDE with the benefit of hovering over values and knowing their type, ctrl-clicking to go to where something is defined, et cetera. The equivalent of this interactive experience for dynamically-typed languages was supposed to be the REPL. But I feel like true REPL-driven development has mostly fallen by the wayside, and most environments don't have great support for it.
Yes, the type system basically replaces a degree of unit tests. You no longer need unit tests that are basically just type checking. And it's more comprehensive too. It's much easier to have incomplete test coverage without knowing it than an invalid type system. With such a type system, the app will fail to compile. Of course, you still need unit tests for many other things, but the type system does remove a class of them.
Whether typechecking or unit tests is "better" is really a question of taste.
That's really the crux of it. Types are a way to talk to the compiler and it will be your primary target. Humans will also benefit somehow, but it's secundary and they won't be the one who'll give you a tough time.
I see it:
- strict types guarantee the compiler is happy but you'll need to bend yourself to make sure it actually matches your intent
- loose types help you properly express your intent, but you're one the hook to make sure the interpretor does what you said.
It’s an interesting example, knowing when to make a breaking change is really tricky in codebases like these.
You could avoid it by leaving “name” and just adding the additional fields. Maybe that’s the “right” thing to do if you have a ton of consumers and spotty test coverage. But if there’s a lot of fragile code out there (parsing “name” in subtlety different ways, etc.) it’ll never be fixed/improved. Things can get gnarly over time because there’s multiple ways to do something, and everything starts to feel like “legacy code”.
I tend to think that for private codebases, it’s important for breaking changes to be easy.
If your only exposure to static typing is six months of TS, what you are missing is experience. You're still on the learning curve and thus the cognitive load of explicit types is high, but with time the opposite becomes true.
Typescript is not suitable for all applications. I also transitioned to Typescript from python some years ago and the extra information about "intent" provided by types made a world of difference to me when reading code written by other people. Type information is so valuable in a team of 3+ developers and when a rest api has at least 5+ resources. If your application or team size are any less, the benefits of typescript might not be obvious. I think that could be what you're experiencing.
In a team, it is just so much easier to come across a "typed" function as opposed to an untyped one. You need to read the entire function to know what it is about.
I'm on the fence. On the one hand I love the power of TS types that they can expose. On the other, I've seen a class of dev that, instead of relying on simple types, will try and make a monstrosity type that does unholy things.
I feel a little bit about it like I feel about RegEx. Small and simple are good, but once you start diving into the entire catalog of regex capabilities for a single expression you've made a mistake.
Id guess its a combination of good editor support for implied types (eg vscode is first-rate at this), plus you likely lean heavily into the test suite when you could likely remove some tests and replace them with better types.
I've only used TS a bit, but I find the typing (and everything else) to be vastly more difficult/convoluted than in Go. Though, using Deno does help a lot (not necessarily with typing though).
> LSP (Language Server Protocol) was the final nail in the coffin of Emacs for me.
It was the opposite for me. Emacs + LSP + many other common conveniences all bind together so beautifully and efficiently that I can't imagine using any other IDE at this point.
Indeed. I'm a long time Emacs user (emacs-nox in the terminal). After spending a day getting LSP and eglot working, I'm loving the "IDE" experience. Reliable indentation, auto-complete, intelligent var renaming, real-time(ish) warnings and error messages, etc, all the good stuff. I'd been ignoring IDE features in Emacs for years, thinking I didn't need them, but I have to admit they're a luxury I don't want to give up.
And I have to say that the whole trope of "Emacs may be able to do anything but you have to configure a lot to get it to work" has has got to be pure exaggeration at this point with things like eglot. I had the most painless experience setting up LSP for Java (among many others).
Getting everything set up for jump to definition and find references to work with an existing code base ... can be a journey.
If you are at a Visual Studio / XCode shop (where these things have been set up and work) you will definitely be swimming against the stream trying to get emacs to "speak" that codebase.
Same, Emacs is improving at a rapid rate thanks to its adoption of LSP and Tree-Sitter. Performance is very good, but the async story needs to become better.
It's the muscle memory. I tried really hard to use things like vs code that do a lot of things better (or at least more automatically) and always end up more efficient in emacs.
Fully agree. Had to work in the past with ruby. Loved it but type errors during runtime where a thing and therefore I would never use ruby in production again.
Lack of types is one thing that turned me away from Elixir when I was trying to learn it.
I didn't know how to think about the types so I wanted some way to annotate them to help think through it, but went through it. And then the compiler complained at me I was passing in the wrong type to a function. I mean yes thanks? But also give me a way to figure that out BEFORE I try running the code.
Typing not being a natively ruby thing makes all efforts to type the language in place seem second class. Ruby needs a typed variant that compiles down to type less Ruby.
This was always true, to be honest. Statically typed languages have always been better. Free IDEs such as Eclipse have been available for a long time. Good JVM languages such as Scala have been available for a long time.
If only the Ruby ecosystem had adopted Scala instead of Ruby, with cutesy books and eccentric underscored personalities, history might have been different.
Ruby was a great little niche language until Rails showed up.
Scala did not exist when Rails was written/extracted. When Scala was released, it did not include any of the things that made Ruby a good choice for Rails.
I’m honestly still stunned at the self-implosion of the Scala community… can’t think of any other language that threw away such quite unexpected success at an industry level. Apart from the toxic community, not trying to challenge Python for supremacy in the scientific computing/data analysis space seems like the major mistake, given it was for a time the lingua franca of data infra (Spark, Scalding etc).
Oh for sure, I'm not too stressed by it -- but I think the ship has sailed on the chance for mainstream Scala adoption. Perhaps it was always delusional, but there was a period when it really seemed like Scala had somewhat of a chance to be the Ruby replacement and become one of the main backend languages (after the Twitter rewrite to Scala, when Foursquare, Meetup and various other startups of that generation were all in on the language); then there was a generation where at least it was the defacto language for data infra. Now, I'm not even sure if many major companies are even using it for the latter case.
Mainstream adoption isn't everything and I still mostly use Scala for personal projects, but it's such a different world working in a language where the major open source projects have industry backing. The Scala community, meanwhile, seems mostly stuck starting entirely new FP frameworks every other week. Nothing against that, but I don't see that much advantage to choosing Scala over OCaml at this point (if you don't need JVM integration).
Momentum appears to be behind Rust now, of course, but I've yet to be convinced. If it had a better GPU story and could replace C/C++ entirely I'd be on board, but otherwise I want my everyday language to be a bit closer to Python/Ruby on the scale against C/C++.
During my first Introduction to Programming course at university, I was taught Java. One thing that I found very troubling is that it wasn't easy, or possible in many cases, to change the programming language. Sure, you can write new functions or methods or classes, but I can't change the keyword for an if-statement. I also remember the TA saying "why would you want that?" I caught myself thinking "if we can program a computer, then why can't we program a language?"
15 years later, I still have this issue a bit, except I made my peace with it. It is what it is. There are some exceptions though! Such as: Lisp, Smalltalk and similar languages. It's in part why I have worked for a company that professionally programmed in Pharo (a Smalltalk descendant [2]). I remember hacking a very crude way for runtime type checking in Pharo [1], just for fun.
I'm not a Ruby programmer, all I know is that Ruby has some things that are identical to Smalltalk. But my question to the author would be: if you long for things like keyword arguments, type hints and namespaces why don't you program it in the Ruby language yourself?
Or is that really hard, like most other languages?
The language is the easy part. Getting tool support for your language change is the hard part. Getting the library ecosystem to adopt it is even harder.
I think that's why extremely flexible languages have seen limited adoption - if your language is more of a language construction kit where everyone can implement their own functionality, everyone has to implement their own tool support (or, more likely, live without any) and there's a limit to how far you can go with that. The best languages find the sweet spot where they give you enough flexibility to implement most reasonable programs, but are still constrained enough that tools can understand and work with all possible code.
Lets not equate silly and possibly dysfunctional string substitution macros with macros in higher level languages, which let you inspect and act according to the structure of the AST.
But that's an implementation issue. Do you really want to say, work on a project where somebody renamed "if" to "wenn" because they thought writing code in German would be neat?
If you want to make a special use tool, you can write a function like custom_if(cond, then_callback, else_callback) in most languages.
Maybe I'm just getting old, but as time goes by I like it more and more when things are obvious and non-magical.
I would rather risk the possibility of dealing with a project where someone did something unorthodox and clearly wrong than work with a language that tries to forbid a priori everything it considers unorthodox. Not that I'm out to defend C's awful clumsy macros, but it's no better when the alternative is nothing at all.
> Do you really want to say, work on a project where somebody renamed "if" to "wenn" because they thought writing code in German would be neat?
Ha, no, I wouldn't. Because to me it is important, that anyone can read the code, not just me or a German speaking developer. Just like I wouldn't translate "if" to Chinese. The point is, that this translation serves no purpose. Or rather its benefit, if any, is not sufficient to justify it being done and deviating from the standard. Macros can be useful and justified. But these string substitution macros ... Meh, rather rarely, I guess. Maybe for when you don't have "true" and "false" or something.
Some useful examples of macros are: Threading/pipelining, timing, function contracts. Things where the order of evaluation needs to change. Things one cannot easily do with functions alone. Your example of "custom_if" is actually a good one, depending on what "custom_if" does. If it needs its arguments to not be evaluated, unless an earlier argument meets some criteria, it would be a good candidate for a macro. Those things are not done using string substitution macros.
There is metaprogramming support in Java, but it's not as inviting and friendly as hygienic macros or Ruby patching. The obvious example is reflection, with which you can do a lot of bizarre things, some of which are useful sometimes. Another is annotations, which is heavily used by libraries in a way similar to how macros are used in certain Lisp like languages.
The reason “custom” programming languages (sometimes called macros) are not popular is that (statistically) no one wants to learn a custom language for each project. People want to learn the basics as few times as possible. Orgs prefer to standardize, most businesses are not snowflakes.
It can be done, but it is not economical, and therefore not practical.
> I also remember the TA saying "why would you want that?"
Is a typical response of someone without the background and without the imagination. It may well be, that doing Java-only for too long robs one of both. An alternative response could have been: "What a fascinating idea! How would you apply that? / What would you do with that?"
I am happy for you, that you found the exceptions and that your picture of the computer programming world is not as incomplete and bleak as that of the TA back then.
As someone who spent a fair chunk of time as a TA for comp-sci classes...
If you're in my room asking me how to change the keyword for "if"...
"why would you want to do that?" is entirely the appropriate answer.
---
Not because the concept of modifying the language itself is necessarily bad, but because the structure of the relationship here isn't neutral. The students in the class are here asking me questions that almost always relate to homework, exams, projects, or tasks.
From experience - You usually see this type of question when the student is actually trying to accomplish some other goal, and can't figure out how to do it.
So "Why would you want to do that?" is not dismissive... it's goal seeking.
help me understand the goal you have in the context of the current work that you are seeking to accomplish by modifying the "if" keyword. Until I understand what you're working towards, it's hard for me to appropriately scope my answer.
Further... I'm not really paid to spitball conceptual language design with you (fun though it may be!). I'm here because a large chunk of the students in the class are genuinely struggling and need basic assistance understanding the existing class concepts.
> Python is not my favorite programming language. In fact, allow me to drop the euphemism and express my pure, unadulterated thoughts about it: I never liked Python, I see it as a huge red flag and I think the world would be a better place if we all decided to finally move on from it.
Why do people make hating a tool their entire personality? I have noticed this same thing with languages like Go ("oh no Go still bad") and C++. I don't like C++ myself but I don't hate it. It would be like hating a screwdriver.
If you don't like a language simply don't use it, there are hundreds of alternatives. If your employer is making you write in that language you should hate your employer, not the language.
If you do carpentry and you've previously lost a finger using a saw without a saw-stop, and now table saws with saw-stops are an option, you might rightly hate using table saws without one, to the point you wouldn't be willing to work at a shop that forces you to use one.
The commenter above asked why people hate tools and compared it to a type of screwdriver ("It's not the tools fault, just use the proper tool")
My point is that get hurt by tools that have footguns, and they realize there are superior tools that don't have the footguns, they may come to hate the tool that they cut themselves on
Sorry, I understood but I'm not sure if the analogy is falling apart. My point was that's okay to use a language and determine it's not for you. It's even fine to write a blog post or two with your learnings and takeaways to warn others. It's not okay, as the gp put it, to make hating the language your entire personality.
This problem extends to other domains as well, I'm not sure why as a society we tolerate other people forcing their beliefs on others when they are not being put in harm's way.
If you don't like a language and are going to turn down a job because if their chosen language, that's fine. If you choose not to use a product because of the underlying technology, also fine. It's not fine to demand everyone see things your way and demand they do the same.
> Why do people make hating a tool their entire personality? I have noticed this same thing with languages like Go
It runs very deep for some people. "That's not Pythonic!" or "That's all unreadable line-noise!"
It becomes a kind of language bigotry similar to English-speakers hating a foreign language. And yet, as you rightly say, these are just programming tools.
I suppose that when humans invest in any type of language, they form and protect the orthodoxy.
> If you don't like a language simply don't use it, there are hundreds of alternatives. If your employer is making you write in that language you should hate your employer, not the language.
I think this is tricky. I work in the world of data and, although I like python fine, I'd find it very hard to find a role in my field that doesn't involve, if not working in python, then at least integrating very closely with work data-scientists produce that's in python.
Some languages just have a big dominance in their field. Python has that for data, and javascript for front-end.
Realistically there is only so much mindshare available, especially if you like the kind of language that benefits from extensive tooling. I miss the days when my preferred language was the #1 option in some spaces and had two first-class IDEs available; my life is genuinely worse now that that's no longer the case.
You haven't used many screwdrivers if you don't hate some of them for stripping screws, slicing your hand etc.
Likewise, other ones exist that make jobs super easy - be they having a ratchet, or quick change of bits, etc
Programming languages seem to be a pretty good parallel. Though, I don't see why Python in particular would be hated. It has its bullshit, but it's workable.
I have used many of them. Some of them are easier to use but all of them were useful at some place and time. I still don't understand how anyone can hate a tool. Dislike maybe, but some people really hate some tools with a passion.
This comparison doesn't make much sense. When a person chooses a language for their project it implies a long term commitment. And all the headaches that follows for years to come. It's unlike a screwdriver, which can be easily replaced in the matter of hours.
Eh, I wrote a lot of python back in the day, and it was pretty miserable experience back in the pre-2.6 era. If you had to use it for work, there was a pretty good chance you'd try to steer away from Python on your own time.
Modern python is a much better language across the board, even if the packaging/deployment story still needs some love
In my highly opinionated opinion, the parallels are:
- Flat head: Perl. Functional, and revolutionary at the time! But awkward to work with.
- Phillips head: Python. Awesome improvement, much easier to work with. But still annoying in frustrating ways.
- JIS: Ruby. A further refinement that resolves the last annoying 5% of work. (Well, OK, resolves 4 of the last 5%) ... (Also Japanese, coincidentally?)
... and in the real world, when you don't get to choose, you almost always find Phillips/Python. Better is good enough. :)
I'm a python developer, and a big fan of the features with gradual typing etc. This article really highlights for me though, how python has very much changed from the language it was even 5 years ago.
Initially, the celebrated feature of python was that it allowed easy and fast development for newcomers. There was a joke a long the lines, "I learned python, it was a great weekend".
As much as I like python's type system (and wouldn't want to see them ever go way!), part of me wonders if moving into a world where hello-world can look like this, is a world where python is no longer the "lean in a weekend" language:
from typing import Annotated
import typer
app = typer.Typer()
@app.command()
def main(
name: Annotated[str, typer.Option("--name", "-n")],
) -> None:
"""Prints out 'HELLO {name}!!' (name upper cased) to the console"""
print(f"HELLO {name:upper}!!")
if __name__ == "__name__":
app()
(obviously the example is silly, and I know this is a lot more than you need to do, hopefully you get my point though!)
> python is no longer the "learn in a weekend" language
For the last 10 years, Python's evolution has been directed by professional developers working for large software companies, and they've focused on adding features that help them work on million-line Python codebases.
Even if the language was originally intended to be easy to learn and ideal for small programs, that's clearly not a design goal any more.
Is there a language today that’s as easy to understand as the “executable pseudocode” of Python 2.x? I haven’t found one.
Agree but fortunately Python's types are entirely optional!
I'm very familiar with pyright and still I start most of my new projects without types and start sprinkling them in once I have a good base working already. This works so well that every time I pick up a static language I just get turned off by the friction of mandatory types and go back to Python. The only exception is Typescript where I can just Any everything temporarily as well.
All due respect but Typer looks like the kind of library that you want to use after your CLI has enough args that you wouldn't be able to get away with the sort of "Hello World" simplicity that you pine for.
Nobody's stopping you from manually parsing a couple of arguments. I still do it all the time and it's OK. If anything the magic of gradual typing is that you get to use it as necessity arises.
I think your completely fair in pointing out that only a deeply troubled person would write this up as a generic version of "hello world", it's a silly example and I was at least partly going OTT for comic effect.
That said, I think it's true that production python tends to look more like my example, and less `print("hello world")`.
Typer is very convenient. I don't believe anyone manually parses arguments manually in Python where we have argparse in the standard library, and Typer is a step forward from that.
I really want to like typer, and frequently go down the rabbit hole of rewriting all my argparse into typer, but I keep getting put off by it's high import cost and that development seems to be a bit up in the air (see https://github.com/fastapi/typer/issues/678#issuecomment-319...). A shame because otherwise it's a really nice library!
I use pyright with typeCheckingMode: strict and enforce that via checks in CI. You still have the Any/type: ignore escape hatches when necessary. I haven't written fully dynamic Python in years.
Ruby is such an elegant language, but the strong and ongoing hostility to any sort of sensible gradual typing is a real mistake.
I know that the Ruby community loves its clever runtime metaprogramming, but even the most metaprogrammed codebase is still going to consist mostly of plain old in-out methods. And as anyone who's ever typed a dynamic codebase knows, you pick up so much low-hanging fruit, in terms of edge cases and errors, when you slap some types on those. You don't need to type everything, but there is real hostility in Ruby circles to gradual typing, even where it would make sense and wouldn't impose any major costs.
Personally, I've stopped writing Ruby. Short of any pathway to sensible gradual typing, I just can't shake the feeling that every new line of Ruby is instant tech debt. Which is such a shame, since I find real beauty in the language.
I gave Sorbet a red hot go on a decently-sized codebase maybe a year ago. I stopped writing Ruby not long after coming to the conclusion that Sorbet is a dead end. I never seriously considered RBS; the idea of maintaining C-style header files always struck me a kind of nuts from a DRY perspective, and very antithetical to the elegance of Ruby.
I ended up rewriting my Ruby codebase in Elixir (with thanks to some kind pointers here on HN). Elixir has perfectly satisfactory gradual typing via Erlang's dialyzer, which will happily power an LSP right now, and work is underway on a handmade gradual set-theoretic type system. It's a direction of travel I feel more confident in than Ruby's.
I fully agree to the points here, even as a full time ruby lover. Jumping around different languages over the past 10 years really shows staleness in Ruby as a language, even if the ecosystem tries to keep up.
The ergonomics of ruby still have me very much liking the language as it fits how I think, but there are a lot of good developments in the usual neighbors, and I see myself picking up both Python and JS ever more for small projects.
Ruby fully typed would be awesome imo, but I know that goes against a lot of the fundamentals in the language. I just like the syntax and expressiveness of it, but coming from typescript, its just such a bad DX having to work in a large Ruby codebase.
I'm sort of the inverse of this author: I have always liked Python and disliked Ruby. It's true though that python has changed a lot, and it's a mixed bag IMHO. I think every language feature python has added can have a reasonable argument made for its existence, however collectively it kind of makes the language burgeon under the weight of its own complexity. "one way to do it" really hasn't been a hard goal for the language for a while.
I'm really charmed by ML style languages nowadays. I think python has built a lot of kludges to compensate for the fact that functions, assignments, loops, and conditionals are not expressions. You get comprehensions, lambdas, conditional expressions, the walrus operator... most statements have an expression equivalent now.
it seems like, initially, Guido was of the opinion that in most cases you should just write the statement and not try "to cram everything in-line," so to speak. However it can't be denied that there are cases where the in-line version just looks nice. On the other hand now you have a statement and an expression that is slightly different syntactically but equivalent semantically, and you have to learn both. Rust avoids this nicely by just making everything an expression, but you do get some semicolon-related awkwardness as a result.
I feel similar about "weight" in Python. Some people can really overdo it with the type annotations, wanting to annotate every little variable inside any procedure, even if as a human it is quite easy to infer its type and for the type checker the type is already clear. It adds so much clutter and at the end of the day I think: "Why aren't you just writing Java instead?" and that's probably where that notion originates from.
I used to be like that. When I did Java. I used to think to myself: "Oh neat! Everything has its place. interfaces, abstract classes, classes, methods, anonymous classes, ... everything fits neatly together."
That was before I learned more Python and realized: "Hey wait a moment, things that require me to write elaborate classes in Java are just a little bit of syntax in Python. For example decorators!" And slowly switched to Python.
Now it seems many Java-ers have come to Python, but without changing their mindset. Collectively they make it harder to enjoy using Python, because at workspaces they will mandate the most extreme views towards type annotations, turning Python into a Java dialect in some regards. But without the speed of Java. I have had feedback for a take-home assignment from an application process, where someone in all seriousness complained about me not using type annotations for what amounted to a single page of code(, and for using explanatory comments, when I was not given any guarantees of being able to talk with someone about the code - lol, the audacity).
Part of the problem is how people learn programming. Many people learn it at university, by using Java, and now think everything must work like Java. I mean, Java is honest about types, but it can also be annoying. Has gotten better though. But that message has not arrived yet at what I call the "Java-er mindset" when it comes to writing type annotations. In general languages or their type checkers have become quite good at inferring types.
I am not an experienced programmer, but I liked python because of the dynamic typing, but tbh no type hints are a nightmare (as I used to use python). These days I gravitate towards using type hints unless I am using an ipynb because it looks clean, but it can be a little much, it can look quite ugly. Not every usecase needs type hints is what I've learned.
A good compromise can be for example: Have your type annotations in the head of the procedure you are writing. That includes types of arguments and return type. You write it once at the head, and when you need to know you can look it up there, but you don't need to clutter the whole rest of the code. If you write well composing functions, then this will be all you ever need. If you write procedures 300 LoC, then well ... you shot yourself in the foot.
There definitely is an element of shooting oneself in ones own foot, but sometimes it seems unavoidable to me, or the effort just isn't worth it e.g. if I am using sklearn or numpy and the return types are ambiguous, then I'd have to overload each function at the head of the file or wrap it although it is clear what it does. What do you think? I think that if it's only my own code, then yes this is certainly avoidable with good composing functions.
I've come to the view that the best flow is to build a system in a dynamic language, and then - once you've got the broad strokes figured out - begin gradually typing it, where appropriate.
You definitely need to have a decent grasp of architecture to make this work - strict FP is very helpful to prevent any early spaghettification - but you ultimately get the best of both worlds this way: rapid iteration for the early stages and type safety once you develop a feel for the system you're building.
I've been doing this in Elixir in the last few months and I've really been enjoying it.
Yep, I agree with this. This is what I usually try in Python. Granted, Python is a way worse vehicle for FP than Elixir is, but I try to keep my procedures as pure functions as far as possible with not too big sacrifices in readability and performance. Most of the time a functional solution can be found, even in Python.
And maybe I am a little bit delusional thinking this, but in my experience, when you think deeply and come up with strict FP solutions, and you know what you are doing, then a lot of type issues don't arise, or are obvious to avoid. The simple fact that one thing you initialize once doesn't change over the course of its lifetime, already avoids tons of mistakes. You simply don't get this "Oh, is at that point in time that member of object x already modified, to be value y?" shit.
> Yep, I agree with this. This is what I usually try in Python. Granted, Python is a way worse vehicle for FP than Elixir is, but I try to keep my procedures as pure functions as far as possible with not too big sacrifices in readability and performance. Most of the time a functional solution can be found, even in Python.
That's really interesting. The last time I wrote any serious Python was back in the Python 2 era, so it's been a hot minute, but that Python certainly didn't feel very amenable to FP. Nice to hear that it's turned a corner. I'll keep an eye out for any use case where I could give FP-flavoured Python a spin.
> And maybe I am a little bit delusional thinking this, but in my experience, when you think deeply and come up with strict FP solutions, and you know what you are doing, then a lot of type issues don't arise, or are obvious to avoid. The simple fact that one thing you initialize once doesn't change over the course of its lifetime, already avoids tons of mistakes. You simply don't get this "Oh, is at that point in time that member of object x already modified, to be value y?" shit.
I very much agree with this, and I wish more FP evangelism focused on the many wonderful emergent properties of FP systems in production, instead of cutesy maths cleverness that turns off more people than it attracts (and those that it attracts were always going to be functional programmers to begin with).
To be clear, Python is still no good for FP, when comparing to other languages, including the aforementioned Elixir. Even though the Python ecosystem is huge, few people if any seem to spend any thought on functional data structures. And also you wouldn't be able to use them like in typical FP languages, because you don't get tail call optimization. You can only try your best in Python.
Ruby has a unified interface for select/map/reduce over all containers. They do lazy calculations if specified. You can chain expressions simply by appending them at the end without scrolling to the back of the expression. That is objectively better than lisp and python.
Sure, you can always rewrite to match that style with macros in lisp and generators in python, but they weren't meant to be used that way.
Sad thing about ruby is how they failed to do typing. I love python's typing module. I think it is the single best thing about python and I wouldn't touch python with a pole if it didn't have that module.
What I meant was how (op modifier *params) "homomorphism" is praised by literally everyone and their dog and it is simply worse than params.op1(modifier).op2(modifier)
There's a reason why just about every Lisp currently in use[1] has `->`/`->>`[2] macros: they're just so easy to write that not having them would be strange. Same with |> in OCaml - with currying, it's literally just `let (|>) x f = f x;;`. So, while it's true that Lisp (or OCaml) base language doesn't have the particular convenience, it's also true that in Lisp (and, in this case, in OCaml) you are meant to extend the language to allow such conveniences.
[1] With a glorious exception of PicoLisp, because quote is all you'll ever need.
I still like Ruby. 15+ years in, I find myself in the camp of not wanting it to change. 25 year old me would have been totally jazzed about the addition of namespaces in Ruby 3.5/4.0. 40 year old me wants namespaces to get off my Ruby lawn.
In your camp, waving a flag. I love ruby's simplicity when it comes to rapidly prototyping something, and find the wails about production type errors puzzling.
Only thing I've come near that gave me as much joy was Elixir, and I simply didn't have time to pick it up more than the most generic basics.
Doesn't Ruby essentially already have namespaces, in terms of having modules? If one has proper modules, why would one ever need an alternative, weaker, concept for referring to things?
Right. Today Ruby has essentially a global namespace, where every defined module/class/const is put in the same "global dumping ground" and can override/"monkey patch" each other.
Ruby 3.5 will introduce a new language keyword "namespace" that scopes behavior to that namespace.
class Foo
def foo; puts "foo"; end
end
namespace Bar
class Foo
def foo; puts "bar"; end
end
Foo.new.foo #=> "bar"
end
Foo.new.foo #=> "foo"
Fun times.
This is intended for isolated code loading similar to "modules" in Python or ES6, but I am worried it will be abused badly. I'm also unsure whether they will add a "use Namespace" construct...
As someone who loves Scheme (author of a Scheme exenstion for computer music, Scheme for Max), and who has done lots of Python and little Ruby, I find this odd. To me, Ruby is a much further departure from Scheme. At least in Python I can do something close to functional programming with primitives, though I don't get symbols. Ruby's "everything is an object" has always seemed to me to be even a further departure.
But then I really don't know Ruby, so happy to be told why this is wrong...
What looks like stagnation to Steen is actually [1] Matz’s remarkable foresight that provided stability and developer happiness.
Steen’s not wrong that Python evolved and Ruby moved slower, but he’s wrong to call Ruby stagnant or irrelevant. Just think what we've enjoyed in recent times: YJIT and MJIT massively improved runtime performance, ractors, the various type system efforts (RBS/Sorbet etc) that give gradual typing without cluttering the language etc.
Ruby’s priorities (ergonomics, DSLs, stability) are different[2] from Python’s (standardisation, academia/data). It’s a matter of taste and domain, not superiority.
My reaction to that part of the post was, “Well, it seems like Python needed to evolve while Ruby was better-designed from the beginning. That’s a failing of Python, not of Ruby.” Language stability is a good thing, which is why I prefer Clojure myself. I know enough Python and Ruby to be dangerous. I’m certainly no expert in either one. That said, Python always struck me as a bit of a hack, but people seemed to resonate with the “indentation is significant” syntax, whereas Ruby felt like it was better designed, taking “everything is an object” to its natural conclusion, similar to Smalltalk, but suffering from performance issues because that means lots of more heavyweight message dispatch.
I'm confused by this post because I think Sorbet satisfies basically all the things the author wants, and my experience with Sorbet has been really good!
It pales in comparison to what the author is talking about, editor support for instance is not good, it took them an awful lot of time to add support for linux-aarch64, it's in general rough around the edges (having to maintain various custom type files for some gems it cannot auto-generate type info for) and in general feels like a chore to use.
Yeah Python with uv and Pyright is downright tolerable. As long as you don't care at all about performance anyway (and can guarantee that you never will in future).
I'm going through the same processes as the author, after about a decade of Ruby I'm writing Typescript, Go and type-hinted Python, and I don't think I want to go back to the lack of namespaces/packages and the lack of typing.
And I've actually used Sorbet with Ruby for 2 years, but that seems like a really bad solution to this problem.
I understand your point and it's valid. I think RBS is an interesting attempt at typed Ruby while also keeping it backwards compatible. I do however believe that having it as an separate file is not the right solution, it adds more load to the developer and ruins the joy of Ruby
RBS-inline is an interesting attempt at solving it!
Languages come and go. There was a time when there was a huge momentum behind Ruby (and Rails). It is not (sadly) the case anymore. It is a matter of traction. C'est la vie. I remember back in the 90s there was great interest in Delphi (Borland OO language) but then came Java. I don't even know if someone is still coding in Delphi. I guess Ruby will eventually go the same way.
Typescript is a workaround.
It exists because web apps got more complex and browsers only support JavaScript.
So developers need to stick to JavaScript, but they need typing, therefore TypeScript has been implemented.
It’s an exception where it made sense to do so. For all other languages: if you use some dynamic language and you need typing, either wait until the language supports types natively (PHP‘s approach) or „just“ change the language.
The additional complexity of an additional typing layer is huge. The complexity of TypeScript - and in general JavaScript‘s ecosystem - is incredibly huge.
The biggest issue we have in software development is not that a language isn’t elegant, or you can’t write some some in 3 instead of 15 lines… the biggest problem is complexity. Developers too often forget about that. They focus on things that don’t matter. Ruby vs Python? It doesn’t make a real difference for web apps.
If you want a language and ecosystem with low complexity try Go. It’s not perfect. It’s not elegant. Or PHP, which has a lot of drawbacks, but overall less complexity. I don’t say Go or PHP are the best languages out there, but you should try them to get a picture - to decide for yourself what’s important and what not.
I’m pretty sure he understands that keyword arguments are part of Ruby. But they require special syntax. He appreciates that in Python _any_ argument is a keyword argument if you (the caller) want it to be.
"I consider TypeScript to be the gold standard when it comes to type systems on top of dynamic languages."
Doubly weird, considering that TypeScript work was inspired by typed/racket, and TypeScript doesn't have a sound type system afaik and the OP's first love was Scheme.
This reads like a love letter to programming languages. You can never truly talk
about the quirks of a language without dabbling in it. And without experience with other languages - I doubt some of these quirks might even be seen as such.
Error handling for instance has always been my pet peeve. With dynamic languages like python, errors were all "exceptions". But then golang came along and decided
they'd be values and that only the truly exceptional errors should be "panicked".
But the if err != nil syntax became super verbose only after I learned about the
Result<Ok, Err> from Rust and the matching syntax associated with it.
A lot of people are shocked when they learn about ruby's monkey patching. I for one never truly groked the packaging of python applications until uv came along
to deliver an experience similar to npm.
And I agree, Typescript is the state of the art as far as static typing on top of a dynamic language is concerned. But I never considered it a programming language.
More like a tool to assist developers write/manage large javascript code.
In the end, I think the true reason for returning to pythong probably had more to do with getting a python gig. I live in the part of the world where my tech stack
ended up being influenced early on by the places I worked. I didn't mind learning Typescript for my first gig or improving my skills with nodejs.
In the end, every language can really get things done. And Typescript helps me pay
my bills and I couldn't be more grateful. Learn to love the quirks of your language and stop comparing it unfavorably with others. To date, I've never seen a language as elegant as ruby. Nor do I find an ecosystem better than python's at data science.
I started my programming journey with VB6. Ruby reminds me of Visual Basic. But its never evolved past that. Python became popular because it was a scripting language that had classes and a nice built out framework of features. But it has a god awful syntax and should never be used for a large project. Also their package management is a dumpster fire.
This brings me to C#. At .NET 10 there will be another option then python. .NET 10 brings the ability to run C# script files without the need for a proj file or a main method. This will bring the full .NET Framework and NUGET eco system with it. I can't wait to replace all my python scripts with this.
I was a full-time Rubyist for a long time. I started the UK's first dedicated Ruby on Rails consultancy in 2006 before Rails was even v1.0 (IIRC the first apps I shipped back then were 0.8.6). I stuck around through the hype chain, and then started to help one employer break up a RoR monolith into micro services and adopt Java and Go (this was a mistake - we should have crafted the monolith better). I've built 4 startups as hands-on CTO with Ruby and Rails. It fed and housed me for many years.
In the last 5-7 years I've had to go in other directions. Clojure, Python, Java, even back to C and taking a look at Rust and Zig. I'm now in a role where I don't code so much, but I can see Ruby's problems - performance, the surprises, the fact it allows idiots to do idiotic things (nobody under the age of 40 should be legally allowed to monkey patch a base class or engage in meta programming).
And yet when I want to do something for me, for fun, perhaps advent of code, or a quick mock-up of something that's rolling around in my head, I reach for Ruby. Not elixir which has better runtimes, or C or Zig or Rust which has better performance, not something statically typed which leads to fewer bugs, not Python which has a huge data science community to it...
A few weeks ago I was listening to the DHH episode of the Lex Fridman podcast [0], where DHH talks about Ruby as a "luxury programming language". This matches my own experience.
When I need something to be fast, it's either because I'm dealing with a low-latency problem (and some of my side projects are very latency sensitive - 5ms can make the difference between success and failure), or because I can't afford the luxury of Ruby and Rails and the developer ergonomics.
Ruby makes things fun for the programmer. That's the point. It's beautiful to work with, even if it doesn't do all the things that all the coding books and blogs insist I should be ashamed to not have in my language.
I was slightly embarrassed to be a Ruby and RoR advocate for a while because of the brogrammer BS that emerged around both ecosystems in the 2010s. I then became very embarrassed because it wasn't as cool as a systems language like Rust or Go, or as intellectually deep as Haskell, or as hot on the ML bandwagon as Python.
But I think I don't care anymore. I'm just going to accept it for what it is, and lean into. Life's too short for "shoulds" - I'm just going to like what I like. And I like Ruby.
Ruby feels like a luxury manual hand saw. It fits in the hand perfectly. But it would not be my first choice for every project.
Languages like C#, Java, C++, Scala, Kotlin, and Python in 2025 feel like industrial computerized bandsaws with twenty different settings and controls. Some more complicated than others. They can be tuned to crank out a factories needs but you could spend days just fussing with a single setting that isn't right.
That being said, modern Python to me feels like the least thought out of these. It has been incrementally changed from one language to another, while being forced to keep many of the worst parts of both. To be honest, I think they should keep up the "breaking backwards compatibility" trend and make Python 4 an optionally-compiled, statically-typed language more like Go, but with more expressivity than Go.
I suppose F# is already like my ideal Python 4. It's possible to run as a script or compiled binary. It's a nice clean syntax, and the type system is a joy to use.
A valid F# program can be a single line script or dozens of configuration files. This let's the developer use it for quick and dirty work, then progressively tweak settings to run in a more industrial scale setting.
I've been on a similar journey. I was deep into rails early in my career. Then I moved on, especially liking typescript. I thought I wouldn't go back. But you don't always get the choice, a great job came up and it was a rails app. I found joy in it again - and I'm still there nearly 10 years on. Ruby feels like how OOP should be, it's so very easy to implement patterns that other languages make verbose and horrible. I'm guilty of a lot of metaprogramming, hope you forgive me, I am over 40. I think it can be an undervalued super power of the language: something isn't working or you need deeper insight, just break into the innards of any library you're using and insert logging and/or your own code.
Anyway, that said, for new personal projects I like typescript and rust. But recently I needed to stick an admin interface on such a project and rails shines there, you can get something good and secure stood up with less code and faff than anything else. In today's world of LLMs that is helpful too, rails is concise and old and has lots of open source projects to pull from, so AI breezes through it.
It's the never ending "end"s that bother me about Ruby.
class Mess
def chaos(x)
if x > 0
[1,2,3].each do |i|
case i
when 1
if i.odd?
puts "odd"
else
puts "even"
end
when 2
begin
puts "trying"
rescue
puts "failed"
end
else
puts "other"
end
end
else
puts "negative"
end
end
end
Clear away all those ends and the program logic pops out. Much fresher!
class Mess:
def chaos(self, x):
if x > 0:
for i in [1, 2, 3]:
match i:
case 1:
if i % 2 == 1:
print("odd")
else:
print("even")
case 2:
try:
print("trying")
except:
print("failed")
case _:
print("other")
else:
print("negative")
The indent in your Ruby code is a bit weird. It should be like this
class Mess
def chaos(x)
if x > 0
[1, 2, 3].each do |i|
case i
when 1
if i.odd?
puts "odd"
else
puts "even"
end
when 2
begin
puts "trying"
rescue
puts "failed"
end
else
puts "other"
end
end
else
puts "negative"
end
end
end
I would have done it this way instead
class Mess
def chaos(x)
if x > 0
[1, 2, 3].each do |i|
case i
when 1
puts i.odd? ? "odd" : "even"
when 2
puts "trying"
else
puts "other"
end
end
else
puts "negative"
end
end
end
Or if you allow me to create a separate private method
class Mess
def chaos(x)
return puts "negative" unless x > 0
[1, 2, 3].each { |i| handle_item(i) }
end
private
def handle_item(i)
case i
when 1 then puts(i.odd? ? "odd" : "even")
when 2 then puts "trying"
else puts "other"
end
end
end
I'll leave aside the unnecessary test for parity as contrived for the example.
This is a question of style; it's possible to write ruby the way you have but with experience you definitely wouldn't.
e.g.
Why wrap the iterator in an if..else..end block? You could replace that with a one line guard statement at the top of the method. No end required.
Why use begin..rescue..end in the middle of your example? Just replace that with a single rescue at the end of the method. Again, no end statement required.
This is like nesting 20 if..else blocks in python and then complaining you don't have a monitor big enough to view it without scrolling. You just wouldn't do it that way.
My hope is that with the new ruby parser rubocop will be more agressive about automatically refactoring examples like this away.
That Python code looks like it was punched in the belly and is about to fall down on itself like a Jenga tower. Additionally, the last `else` is hard to track visually and if you make the slightest error in whitespace (which are invisible characters!), everything breaks.
To each their own. It’s because we all have different preferences that there are so many choices.
I find that the IPython REPL has downright amazing multiline support. I've always been envious of it when using GHCI. But even the standard Python REPL supports multiline paste just fine. So what exactly are you talking about? Rolling your own?
Did you start it with two spaces? That's how HN does code blocks. Two spaces added before each line. The other lines look fine because they were already indented, add two spaces before all of them and it'll look fine.
Back when autocompletion and stuff were only available in Visual Studio/Xcode/Other bug IDEs, I was forced to use Ruby and fell in love with it. It didn't matter what I used as my editor was Sublime. But when VSCode came and language features became democratized, I never touched a type-less language again. Why should someone opt for a language with absolutely no features where one can have autocompletion, typechecking, deep data type exploration, jumping to definitions and implementations? I really think it's a bad choice of Ruby not to care for types. And well we now have Crystal which again makes me question why Ruby? And it’s a shame no language is as beautiful as Ruby, not in features choices, design elegance, balance, beauty of the syntax, joy of programming mindset, not even in the name and logo. I wished Matz rethinked this part.
> it’s a shame no language is as beautiful as Ruby [...] I really think it's a bad choice of Ruby not to care for types
Do you think Ruby could change something so fundamental as dynamic => static typing and still retain its beauty?
The only static typing solution I've seen for Ruby is Sorbet, and it's... not beautiful.
Crystal [0] is what comes to mind. I've spent very little time with it, but I find the syntax very appealing.
[0] https://crystal-lang.org/
I use RBS/steep with great success to catch plenty of nil issues early, but it's similarly not great from a dev POV to have to maintain a completely separate set of rbs files (or special comments with rbs-inline). Also in my experience, modern editors don't leverage it for typing/intellisense.
Sorbet now supports inline RBS signatures, which I find a lot more readable. If you use VS code with Ruby LSP, the syntax highlighting is pretty great for the signatures too.
https://sorbet.org/docs/rbs-support
One of the main devs on Sorbet would agree: https://blog.jez.io/history-of-sorbet-syntax/
Based on what Ive read [1], Sorbet was born out of very specific circumstances at Stripe, in the same sort of way that go was at google. I think it missed the mark due to it being a specific tool for specific usecases instead of an "open source project for the general public" first.
I love ruby but I do look forward to having a good option for types at some point. The community makes up for lack of types by adding more tests to their test suite, which is nice but I'd love to layer some encoding into the codebase itself and save the CI time.
[1] - https://blog.jez.io/history-of-sorbet-syntax/
Static type-signatures are inevitable in general. You can see this by how even the Ruby documentation has to make up silly ad hoc notation like "→ array_of_strings", "→ matchdata or nil" etc.
(Random example): https://docs.ruby-lang.org/en/3.4/String.html#method-i-lines
That's just documentation tool but the method is actually already statically typed by ruby.
I'm not sure why they don't link that in the documenation but I beleive it is something the RDoc mainer wants to do.
https://github.com/ruby/rbs/blob/master/core/string.rbsI think Ruby is really great at what it tries to do. The only "problem" with it is that python kind of sucked the air out of the room and ruby got shoved into a little niche.
These languages are really quite similar in many ways, but their domains ended up diverging. With python becoming the layman / scientific / learning language of choice, ruby has been pigeon holed into mostly web development.
Both are really easy to pick up and learn for somebody unfamiliar with CS concepts and I personally find the ruby syntax far more intuitive.
We have a lot more options now. For a while people tried to use python and ruby as glue / systems programming languages, but with golang and rust you have really good and more performant options in that space. And as you say the tooling has improved massively, so the hurdle of moving on to a more "rigid" language is less than it ever was.
I still really like ruby, and I think rails is still a powerhouse due to solving so many real world problems in a really complete package, but the lack of adoption outside of that niche has left it dwindling in popularity.
Python syntax is more intuitive for someone who initially learned C or Java, and for mainstream developers a point in its favor.
As someone coming from Ruby to TypeScript, I find types cumbersome, verbose, complex, and not of much use. I have been writing and reading TS for the past six months. What am I missing?
I have been building web apps for long enough to remember when it was commonplace to SSH (or Telnet!) into the server and just vim index.php. As you can imagine, it was pretty easy to bring the site down like that, so we started doing what is now called Continuous Integration and Continuous Deployment: automated tests as a precondition for deployment.
Later, we adopted static analysis tools and code linters. This helped, but only so much could be done with the dynamically typed languages which were popular in the early internet.
As a self-taught programmer with no college degree, I started with dynamic languages: Perl, JavaScript, PHP, Ruby. I viewed the statically typed languages (and the programmers who could wield them) with reverence and awe. C, C++, Java, these were “real” languages that required big brains, and I should stay in my dynamically typed playpen. But as I matured, my understanding shifted: dynamic languages require hubris, require belief that you can keep an entire complicated codebase in your head with perfect recall. Statically typed languages, it turns out, invite humility.
With a static type system, each step in a program’s lifecycle can be a little contract with itself. Break that contract and the language will tell you exactly how and where. These languages can provide immediate feedback to the developer about a program’s correctness before it is even run. Bridging the gap between all of the existing dynamic code (and the developers who wrote it) and the statically typed utopia lies so-called gradual type systems such as TypeScript.
I like to use a meteorological analogy here: if JavaScript is “wintry mix,” then TypeScript lowers the temperature just enough to build a snowman.
heh, i got the opposite experience going from static to dynamic.
Typescript is a bad introduction to the world of static types in a lot of ways. Typescript is incredible for what it is doing, but still, at its core, laying down a static type system on top of a language ecosystem that was dynamic for years is fundamentally just, well, a bit whacky compared to a language ecosystem that was static from the beginning.
I've been doing Typescript now for about 9 months, so I wouldn't call myself an expert in that, but I also have decades of experience in many other dynamic and static languages (including Haskell), so I can say with confidence that a lot of features in Typescript are to solve problems unique to the Javascript world, and then there are the features in Typescript that are there to solve problems in the other Typescript solutions, and all-in-all while it has great utility and has many fantastic features it just isn't possible to completely overcome the fact that it's a static type system, on top of a dynamic type system.
I've seen a number of posts to the effect of "I'm coming from Typescript and learning Go, how do I do X" and so often the answer has been "Even in Go, even with its very simple type system by static language standards, the answer is that you don't do X at all because X is a feature on top of a feature on top of a feature designed to deal with being on a dynamic language and when you're on a static language you not only don't deploy that solution, you wouldn't even have the problem that's the solution to except that you just created it for yourself by copying Typescript too closely in some other way." A simple example being you don't ask how to type a map/dict/etc. in a normal static language based on the contents of the "type" value, you just declare a properly-typed value in the first place and always use that, at most converting at the edge.
Typescript is a great tool but a very unrepresentative view on how static typing works.
Very true. I would advise the parent to try something like Haskell as a hobby project. It helps you see the actual benefits of types.
Types really start pulling their own weight as the size of an application increases.
In order to catch problems in dynamic type languages you end up needing a bunch of additional tests to, ironically, verify the expected type. And even then, those tests don't and can't tell you how a method is actually used throughout the program.
Consider the following class
Now imagine you want to refactor this class to something like this All the sudden you've got a problem on your hands. You have to find everywhere that referenced `name` on a `Human` object (And don't mess that up, it could be `name` on the `Pet` object) and change them over to full name, or figure out if they are doing something tricky like trying to extract the first name from `name`.Types make refactoring this sort of code somewhat trivial. You simply change the fields and let the compiler tell you ever position which relied on that field.
This extends into all sort of circumstances, like letting the user of a method know what types are expected. For example, the above ruby code doesn't really guarantee that `first` is a string. In fact, it could be an int or even an object and the code would likely handle it just fine. Types expose interface contracts in a quick and easy way for programmers to understand what they are dealing with.
The classical counterargument to this is that, if you have good test coverage then eliminating @name should lead you directly (via failing tests) to where the name field was being used. This works especially well in codebases which enforce what I tend to call "pseudo"-type systems, for example via clojure's spec and Racket's contracts. Where the shape of data is enforced structurally at runtime.
To be honest, the argument in favor of static typing that I find more compelling is the IDE argument. It definitely is a much richer experience browsing code in an IDE with the benefit of hovering over values and knowing their type, ctrl-clicking to go to where something is defined, et cetera. The equivalent of this interactive experience for dynamically-typed languages was supposed to be the REPL. But I feel like true REPL-driven development has mostly fallen by the wayside, and most environments don't have great support for it.
Yes, the type system basically replaces a degree of unit tests. You no longer need unit tests that are basically just type checking. And it's more comprehensive too. It's much easier to have incomplete test coverage without knowing it than an invalid type system. With such a type system, the app will fail to compile. Of course, you still need unit tests for many other things, but the type system does remove a class of them.
Whether typechecking or unit tests is "better" is really a question of taste.
> let the compiler tell you
That's really the crux of it. Types are a way to talk to the compiler and it will be your primary target. Humans will also benefit somehow, but it's secundary and they won't be the one who'll give you a tough time.
I see it:
- strict types guarantee the compiler is happy but you'll need to bend yourself to make sure it actually matches your intent
- loose types help you properly express your intent, but you're one the hook to make sure the interpretor does what you said.
It’s an interesting example, knowing when to make a breaking change is really tricky in codebases like these.
You could avoid it by leaving “name” and just adding the additional fields. Maybe that’s the “right” thing to do if you have a ton of consumers and spotty test coverage. But if there’s a lot of fragile code out there (parsing “name” in subtlety different ways, etc.) it’ll never be fixed/improved. Things can get gnarly over time because there’s multiple ways to do something, and everything starts to feel like “legacy code”.
I tend to think that for private codebases, it’s important for breaking changes to be easy.
If your only exposure to static typing is six months of TS, what you are missing is experience. You're still on the learning curve and thus the cognitive load of explicit types is high, but with time the opposite becomes true.
Typescript is not suitable for all applications. I also transitioned to Typescript from python some years ago and the extra information about "intent" provided by types made a world of difference to me when reading code written by other people. Type information is so valuable in a team of 3+ developers and when a rest api has at least 5+ resources. If your application or team size are any less, the benefits of typescript might not be obvious. I think that could be what you're experiencing.
In a team, it is just so much easier to come across a "typed" function as opposed to an untyped one. You need to read the entire function to know what it is about.
Totally agree. I've found that people who don't understand the value of types are usually solo devs
Typescript is structurally typed, not nominally typed like C# or C++. Lots more type Olympics.
Have you sat down to actually learn them? If you're still guessing at them, they're going to be hard. I'd recommend https://type-level-typescript.com/
Types in TS can indeed become very complex (depending on their usage of course) and it might not be the best example of the benefits.
I personally think that simpler Rust is a better example of the benefits static typing (or maybe something like Gleam).
I'm on the fence. On the one hand I love the power of TS types that they can expose. On the other, I've seen a class of dev that, instead of relying on simple types, will try and make a monstrosity type that does unholy things.
I feel a little bit about it like I feel about RegEx. Small and simple are good, but once you start diving into the entire catalog of regex capabilities for a single expression you've made a mistake.
> On the other, I've seen a class of dev that, instead of relying on simple types, will try and make a monstrosity type that does unholy things.
Totally agree, however that love of complexity will just squeeze into something else had they not had types to have fun with.
Id guess its a combination of good editor support for implied types (eg vscode is first-rate at this), plus you likely lean heavily into the test suite when you could likely remove some tests and replace them with better types.
I've only used TS a bit, but I find the typing (and everything else) to be vastly more difficult/convoluted than in Go. Though, using Deno does help a lot (not necessarily with typing though).
Some people prefer to find bugs after deployment, I suppose.
LSP (Language Server Protocol) was the final nail in the coffin of Emacs for me.
VSCode was "good enough" for pretty much every language with LSP at that point, I did't even bother with Jetbrains ides outside of work after that.
And when Obsidian replaced org-mode for me, I deleted my .emacs directory from my dotfiles repository.
> LSP (Language Server Protocol) was the final nail in the coffin of Emacs for me.
It was the opposite for me. Emacs + LSP + many other common conveniences all bind together so beautifully and efficiently that I can't imagine using any other IDE at this point.
Indeed. I'm a long time Emacs user (emacs-nox in the terminal). After spending a day getting LSP and eglot working, I'm loving the "IDE" experience. Reliable indentation, auto-complete, intelligent var renaming, real-time(ish) warnings and error messages, etc, all the good stuff. I'd been ignoring IDE features in Emacs for years, thinking I didn't need them, but I have to admit they're a luxury I don't want to give up.
And I have to say that the whole trope of "Emacs may be able to do anything but you have to configure a lot to get it to work" has has got to be pure exaggeration at this point with things like eglot. I had the most painless experience setting up LSP for Java (among many others).
It is in fact pretty true with C++.
Getting everything set up for jump to definition and find references to work with an existing code base ... can be a journey.
If you are at a Visual Studio / XCode shop (where these things have been set up and work) you will definitely be swimming against the stream trying to get emacs to "speak" that codebase.
Same, Emacs is improving at a rapid rate thanks to its adoption of LSP and Tree-Sitter. Performance is very good, but the async story needs to become better.
Same. It's easy to setup and use. As is gptel, aidermacs, and claude code ide.
It's the muscle memory. I tried really hard to use things like vs code that do a lot of things better (or at least more automatically) and always end up more efficient in emacs.
Hopefully emacs can catch up.
> jumping to definitions and implementations
This is undervalued. So frustrating in ruby that this doesnt exist or at least isnt easy.
Fully agree. Had to work in the past with ruby. Loved it but type errors during runtime where a thing and therefore I would never use ruby in production again.
I use kotlin nowadays…
I used Kotlin before it was popular and people laughed at me... And now I use TypeScript...
Kotlin is lovely to work with but holy hell is it slow to iterate on.
What does "slow to iterate on" mean?
Lack of types is one thing that turned me away from Elixir when I was trying to learn it.
I didn't know how to think about the types so I wanted some way to annotate them to help think through it, but went through it. And then the compiler complained at me I was passing in the wrong type to a function. I mean yes thanks? But also give me a way to figure that out BEFORE I try running the code.
Try Gleam!
Typing not being a natively ruby thing makes all efforts to type the language in place seem second class. Ruby needs a typed variant that compiles down to type less Ruby.
This was always true, to be honest. Statically typed languages have always been better. Free IDEs such as Eclipse have been available for a long time. Good JVM languages such as Scala have been available for a long time.
If only the Ruby ecosystem had adopted Scala instead of Ruby, with cutesy books and eccentric underscored personalities, history might have been different.
Not sure how to think about that idea.
Ruby was a great little niche language until Rails showed up.
Scala did not exist when Rails was written/extracted. When Scala was released, it did not include any of the things that made Ruby a good choice for Rails.
I’m honestly still stunned at the self-implosion of the Scala community… can’t think of any other language that threw away such quite unexpected success at an industry level. Apart from the toxic community, not trying to challenge Python for supremacy in the scientific computing/data analysis space seems like the major mistake, given it was for a time the lingua franca of data infra (Spark, Scalding etc).
You _can_ largely ignore the toxicity. Don't give toxic individuals attention, and they go somewhere else to stor the pot.
Just debate the ideas with the merits in the source code, ignore the haters, and be kind and helpful. It's not difficult to do.
Oh for sure, I'm not too stressed by it -- but I think the ship has sailed on the chance for mainstream Scala adoption. Perhaps it was always delusional, but there was a period when it really seemed like Scala had somewhat of a chance to be the Ruby replacement and become one of the main backend languages (after the Twitter rewrite to Scala, when Foursquare, Meetup and various other startups of that generation were all in on the language); then there was a generation where at least it was the defacto language for data infra. Now, I'm not even sure if many major companies are even using it for the latter case.
Mainstream adoption isn't everything and I still mostly use Scala for personal projects, but it's such a different world working in a language where the major open source projects have industry backing. The Scala community, meanwhile, seems mostly stuck starting entirely new FP frameworks every other week. Nothing against that, but I don't see that much advantage to choosing Scala over OCaml at this point (if you don't need JVM integration).
Momentum appears to be behind Rust now, of course, but I've yet to be convinced. If it had a better GPU story and could replace C/C++ entirely I'd be on board, but otherwise I want my everyday language to be a bit closer to Python/Ruby on the scale against C/C++.
There's also Crystal https://crystal-lang.org/
This post reminds me of something.
During my first Introduction to Programming course at university, I was taught Java. One thing that I found very troubling is that it wasn't easy, or possible in many cases, to change the programming language. Sure, you can write new functions or methods or classes, but I can't change the keyword for an if-statement. I also remember the TA saying "why would you want that?" I caught myself thinking "if we can program a computer, then why can't we program a language?"
15 years later, I still have this issue a bit, except I made my peace with it. It is what it is. There are some exceptions though! Such as: Lisp, Smalltalk and similar languages. It's in part why I have worked for a company that professionally programmed in Pharo (a Smalltalk descendant [2]). I remember hacking a very crude way for runtime type checking in Pharo [1], just for fun.
I'm not a Ruby programmer, all I know is that Ruby has some things that are identical to Smalltalk. But my question to the author would be: if you long for things like keyword arguments, type hints and namespaces why don't you program it in the Ruby language yourself?
Or is that really hard, like most other languages?
[1] https://youtu.be/FeFrt-kdvms?si=vlFPIkGuVceztVuW&t=2678
[2] Fun fact, I learned about Lisp, Smalltalk and Pharo through HN! So I know most of you know but I suspect some don't.
The language is the easy part. Getting tool support for your language change is the hard part. Getting the library ecosystem to adopt it is even harder.
I think that's why extremely flexible languages have seen limited adoption - if your language is more of a language construction kit where everyone can implement their own functionality, everyone has to implement their own tool support (or, more likely, live without any) and there's a limit to how far you can go with that. The best languages find the sweet spot where they give you enough flexibility to implement most reasonable programs, but are still constrained enough that tools can understand and work with all possible code.
Too much change isn't good though. There's value in consistent basics. I've seen people doing things like:
because they liked Pascal, and that way lies madness.Lets not equate silly and possibly dysfunctional string substitution macros with macros in higher level languages, which let you inspect and act according to the structure of the AST.
But that's an implementation issue. Do you really want to say, work on a project where somebody renamed "if" to "wenn" because they thought writing code in German would be neat?
If you want to make a special use tool, you can write a function like custom_if(cond, then_callback, else_callback) in most languages.
Maybe I'm just getting old, but as time goes by I like it more and more when things are obvious and non-magical.
I would rather risk the possibility of dealing with a project where someone did something unorthodox and clearly wrong than work with a language that tries to forbid a priori everything it considers unorthodox. Not that I'm out to defend C's awful clumsy macros, but it's no better when the alternative is nothing at all.
> Do you really want to say, work on a project where somebody renamed "if" to "wenn" because they thought writing code in German would be neat?
Ha, no, I wouldn't. Because to me it is important, that anyone can read the code, not just me or a German speaking developer. Just like I wouldn't translate "if" to Chinese. The point is, that this translation serves no purpose. Or rather its benefit, if any, is not sufficient to justify it being done and deviating from the standard. Macros can be useful and justified. But these string substitution macros ... Meh, rather rarely, I guess. Maybe for when you don't have "true" and "false" or something.
Some useful examples of macros are: Threading/pipelining, timing, function contracts. Things where the order of evaluation needs to change. Things one cannot easily do with functions alone. Your example of "custom_if" is actually a good one, depending on what "custom_if" does. If it needs its arguments to not be evaluated, unless an earlier argument meets some criteria, it would be a good candidate for a macro. Those things are not done using string substitution macros.
> that way lies madness.
Flashbacks to scala operator PTSD.
No. I don't want to use ++<>^^%% operator! I am not a number! I'm a man!
Scala3 fortunately fixed those.
Did it? Last time I checked, Scala 3 still has custom operators. The thing I'm making fun of is Scala's custom operator gibberish.
Wasn't one of the old-school unix shells written in this style originally? bash, maybe?
There is metaprogramming support in Java, but it's not as inviting and friendly as hygienic macros or Ruby patching. The obvious example is reflection, with which you can do a lot of bizarre things, some of which are useful sometimes. Another is annotations, which is heavily used by libraries in a way similar to how macros are used in certain Lisp like languages.
https://www.baeldung.com/java-reflection
https://www.baeldung.com/java-annotation-processing-builder
Then you've got the byte code itself, and there be dragons: <https://aphyr.com/posts/341-hexing-the-technical-interview>
While you rarely see byte code shenanigans in Java code bases, it's how some other languages on the JVM achieve things like runtime metaprogramming.
The reason “custom” programming languages (sometimes called macros) are not popular is that (statistically) no one wants to learn a custom language for each project. People want to learn the basics as few times as possible. Orgs prefer to standardize, most businesses are not snowflakes.
It can be done, but it is not economical, and therefore not practical.
> I also remember the TA saying "why would you want that?"
Is a typical response of someone without the background and without the imagination. It may well be, that doing Java-only for too long robs one of both. An alternative response could have been: "What a fascinating idea! How would you apply that? / What would you do with that?"
I am happy for you, that you found the exceptions and that your picture of the computer programming world is not as incomplete and bleak as that of the TA back then.
So your suggestion to the TA is to ask literally the exact same question but slightly different?
One question is encouraging, the other is discouraging. That matters a lot in an educational setting.
And they aren't the exact same question.
As someone who spent a fair chunk of time as a TA for comp-sci classes...
If you're in my room asking me how to change the keyword for "if"...
"why would you want to do that?" is entirely the appropriate answer.
---
Not because the concept of modifying the language itself is necessarily bad, but because the structure of the relationship here isn't neutral. The students in the class are here asking me questions that almost always relate to homework, exams, projects, or tasks.
From experience - You usually see this type of question when the student is actually trying to accomplish some other goal, and can't figure out how to do it.
So "Why would you want to do that?" is not dismissive... it's goal seeking.
help me understand the goal you have in the context of the current work that you are seeking to accomplish by modifying the "if" keyword. Until I understand what you're working towards, it's hard for me to appropriately scope my answer.
Further... I'm not really paid to spitball conceptual language design with you (fun though it may be!). I'm here because a large chunk of the students in the class are genuinely struggling and need basic assistance understanding the existing class concepts.
> Python is not my favorite programming language. In fact, allow me to drop the euphemism and express my pure, unadulterated thoughts about it: I never liked Python, I see it as a huge red flag and I think the world would be a better place if we all decided to finally move on from it.
Why do people make hating a tool their entire personality? I have noticed this same thing with languages like Go ("oh no Go still bad") and C++. I don't like C++ myself but I don't hate it. It would be like hating a screwdriver.
If you don't like a language simply don't use it, there are hundreds of alternatives. If your employer is making you write in that language you should hate your employer, not the language.
If you do carpentry and you've previously lost a finger using a saw without a saw-stop, and now table saws with saw-stops are an option, you might rightly hate using table saws without one, to the point you wouldn't be willing to work at a shop that forces you to use one.
Whose forcing you to work at an unsafe shop?
The commenter above asked why people hate tools and compared it to a type of screwdriver ("It's not the tools fault, just use the proper tool")
My point is that get hurt by tools that have footguns, and they realize there are superior tools that don't have the footguns, they may come to hate the tool that they cut themselves on
Sorry, I understood but I'm not sure if the analogy is falling apart. My point was that's okay to use a language and determine it's not for you. It's even fine to write a blog post or two with your learnings and takeaways to warn others. It's not okay, as the gp put it, to make hating the language your entire personality.
This problem extends to other domains as well, I'm not sure why as a society we tolerate other people forcing their beliefs on others when they are not being put in harm's way.
If you don't like a language and are going to turn down a job because if their chosen language, that's fine. If you choose not to use a product because of the underlying technology, also fine. It's not fine to demand everyone see things your way and demand they do the same.
your landlord.
You left out this part:
> The reasons behind this choice of employment are very much unrelated to the technology stack.
Programming language isn't the only factor for employment. People don't always get to just change jobs when an aspect isn't ideal for them.
On top of that, python is ubiquitous in some sectors. It's not as easy to avoid as a lot of other languages.
> Why do people make hating a tool their entire personality? I have noticed this same thing with languages like Go
It runs very deep for some people. "That's not Pythonic!" or "That's all unreadable line-noise!"
It becomes a kind of language bigotry similar to English-speakers hating a foreign language. And yet, as you rightly say, these are just programming tools.
I suppose that when humans invest in any type of language, they form and protect the orthodoxy.
Their entire personality, really? You got that from a single paragraph from a blog post?
I know why. People can hate tools if the tools are frustrating to use. The frustration causes anger and hate.
I know this is hard to understand for logical genius savants like you. A lot of HNers are like that and don’t understand human emotions.
> If you don't like a language simply don't use it, there are hundreds of alternatives. If your employer is making you write in that language you should hate your employer, not the language.
I think this is tricky. I work in the world of data and, although I like python fine, I'd find it very hard to find a role in my field that doesn't involve, if not working in python, then at least integrating very closely with work data-scientists produce that's in python.
Some languages just have a big dominance in their field. Python has that for data, and javascript for front-end.
Realistically there is only so much mindshare available, especially if you like the kind of language that benefits from extensive tooling. I miss the days when my preferred language was the #1 option in some spaces and had two first-class IDEs available; my life is genuinely worse now that that's no longer the case.
Humans have a very strong tendency to identify with certain groups (while distancing from other groups).
This is just one such example and it's similar to how group dynamics with sport teams and politics work.
You haven't used many screwdrivers if you don't hate some of them for stripping screws, slicing your hand etc.
Likewise, other ones exist that make jobs super easy - be they having a ratchet, or quick change of bits, etc
Programming languages seem to be a pretty good parallel. Though, I don't see why Python in particular would be hated. It has its bullshit, but it's workable.
I have used many of them. Some of them are easier to use but all of them were useful at some place and time. I still don't understand how anyone can hate a tool. Dislike maybe, but some people really hate some tools with a passion.
This comparison doesn't make much sense. When a person chooses a language for their project it implies a long term commitment. And all the headaches that follows for years to come. It's unlike a screwdriver, which can be easily replaced in the matter of hours.
I pretty much hate any screwdriver that's not a Robertson.
Eh, I wrote a lot of python back in the day, and it was pretty miserable experience back in the pre-2.6 era. If you had to use it for work, there was a pretty good chance you'd try to steer away from Python on your own time.
Modern python is a much better language across the board, even if the packaging/deployment story still needs some love
> It would be like hating a screwdriver.
I hate Philips screwdrivers (but love JIS).
Exactly!
Both Python and Ruby are screwdrivers.
In my highly opinionated opinion, the parallels are:
... and in the real world, when you don't get to choose, you almost always find Phillips/Python. Better is good enough. :)Someone once said,
"Ruby is Scheme mated with Perl in such a way that the best genes of both failed to exert a phenotype."
For internet points!
Especially strong from someone using Ruby, which is basically Hipster-Python.
/s
> Hipster
Is it 2007 again already?
I'm a python developer, and a big fan of the features with gradual typing etc. This article really highlights for me though, how python has very much changed from the language it was even 5 years ago.
Initially, the celebrated feature of python was that it allowed easy and fast development for newcomers. There was a joke a long the lines, "I learned python, it was a great weekend".
As much as I like python's type system (and wouldn't want to see them ever go way!), part of me wonders if moving into a world where hello-world can look like this, is a world where python is no longer the "lean in a weekend" language:
(obviously the example is silly, and I know this is a lot more than you need to do, hopefully you get my point though!)> python is no longer the "learn in a weekend" language
For the last 10 years, Python's evolution has been directed by professional developers working for large software companies, and they've focused on adding features that help them work on million-line Python codebases.
Even if the language was originally intended to be easy to learn and ideal for small programs, that's clearly not a design goal any more.
Is there a language today that’s as easy to understand as the “executable pseudocode” of Python 2.x? I haven’t found one.
Agree but fortunately Python's types are entirely optional!
I'm very familiar with pyright and still I start most of my new projects without types and start sprinkling them in once I have a good base working already. This works so well that every time I pick up a static language I just get turned off by the friction of mandatory types and go back to Python. The only exception is Typescript where I can just Any everything temporarily as well.
All due respect but Typer looks like the kind of library that you want to use after your CLI has enough args that you wouldn't be able to get away with the sort of "Hello World" simplicity that you pine for.
Nobody's stopping you from manually parsing a couple of arguments. I still do it all the time and it's OK. If anything the magic of gradual typing is that you get to use it as necessity arises.
I think your completely fair in pointing out that only a deeply troubled person would write this up as a generic version of "hello world", it's a silly example and I was at least partly going OTT for comic effect.
That said, I think it's true that production python tends to look more like my example, and less `print("hello world")`.
Typer is very convenient. I don't believe anyone manually parses arguments manually in Python where we have argparse in the standard library, and Typer is a step forward from that.
I really want to like typer, and frequently go down the rabbit hole of rewriting all my argparse into typer, but I keep getting put off by it's high import cost and that development seems to be a bit up in the air (see https://github.com/fastapi/typer/issues/678#issuecomment-319...). A shame because otherwise it's a really nice library!
Personally, I would absolutely not mind it if Python made type annotations required.
I use pyright with typeCheckingMode: strict and enforce that via checks in CI. You still have the Any/type: ignore escape hatches when necessary. I haven't written fully dynamic Python in years.
You're going to love the equivalent Haskell example.
You can't leave me hanging and not give it!
[dead]
Ruby is such an elegant language, but the strong and ongoing hostility to any sort of sensible gradual typing is a real mistake.
I know that the Ruby community loves its clever runtime metaprogramming, but even the most metaprogrammed codebase is still going to consist mostly of plain old in-out methods. And as anyone who's ever typed a dynamic codebase knows, you pick up so much low-hanging fruit, in terms of edge cases and errors, when you slap some types on those. You don't need to type everything, but there is real hostility in Ruby circles to gradual typing, even where it would make sense and wouldn't impose any major costs.
Personally, I've stopped writing Ruby. Short of any pathway to sensible gradual typing, I just can't shake the feeling that every new line of Ruby is instant tech debt. Which is such a shame, since I find real beauty in the language.
> Ruby is such an elegant language, but the strong and ongoing hostility to any sort of sensible gradual typing
I'm not sure what you're basing that on, gradual typing has been built into Ruby since 3.0 (2020). Sorbet can around a bit earlier in 2019.
There is on going work to improve both with RBS-Inline support now in Sorbet with runtime support hopefully on the way.
IRB uses RBS for autocompletion and Solargraph and Ruby-LSP both support it.
I would say there is no hostility to gradual typing in the Ruby community. Quite the opposite, people are being paid to work on it.
Have you given RBS/Sorbet etc a go?
I gave Sorbet a red hot go on a decently-sized codebase maybe a year ago. I stopped writing Ruby not long after coming to the conclusion that Sorbet is a dead end. I never seriously considered RBS; the idea of maintaining C-style header files always struck me a kind of nuts from a DRY perspective, and very antithetical to the elegance of Ruby.
I ended up rewriting my Ruby codebase in Elixir (with thanks to some kind pointers here on HN). Elixir has perfectly satisfactory gradual typing via Erlang's dialyzer, which will happily power an LSP right now, and work is underway on a handmade gradual set-theoretic type system. It's a direction of travel I feel more confident in than Ruby's.
I fully agree to the points here, even as a full time ruby lover. Jumping around different languages over the past 10 years really shows staleness in Ruby as a language, even if the ecosystem tries to keep up.
The ergonomics of ruby still have me very much liking the language as it fits how I think, but there are a lot of good developments in the usual neighbors, and I see myself picking up both Python and JS ever more for small projects.
Ruby fully typed would be awesome imo, but I know that goes against a lot of the fundamentals in the language. I just like the syntax and expressiveness of it, but coming from typescript, its just such a bad DX having to work in a large Ruby codebase.
> I know that goes against a lot of the fundamentals in the language.
You're not the only person to reply with something like this but just to repeat, Ruby has had official gradual static typing support for 5 years now.
Gradual typing is fundermentally already part of Ruby.
Ruby could do with better static analysis tooling but people are being paid to work on that.
What do you mean with "gradual typing"? People want to know what data type a function takes and what a variable is. That's it really.
I'm sort of the inverse of this author: I have always liked Python and disliked Ruby. It's true though that python has changed a lot, and it's a mixed bag IMHO. I think every language feature python has added can have a reasonable argument made for its existence, however collectively it kind of makes the language burgeon under the weight of its own complexity. "one way to do it" really hasn't been a hard goal for the language for a while.
I'm really charmed by ML style languages nowadays. I think python has built a lot of kludges to compensate for the fact that functions, assignments, loops, and conditionals are not expressions. You get comprehensions, lambdas, conditional expressions, the walrus operator... most statements have an expression equivalent now.
it seems like, initially, Guido was of the opinion that in most cases you should just write the statement and not try "to cram everything in-line," so to speak. However it can't be denied that there are cases where the in-line version just looks nice. On the other hand now you have a statement and an expression that is slightly different syntactically but equivalent semantically, and you have to learn both. Rust avoids this nicely by just making everything an expression, but you do get some semicolon-related awkwardness as a result.
I feel similar about "weight" in Python. Some people can really overdo it with the type annotations, wanting to annotate every little variable inside any procedure, even if as a human it is quite easy to infer its type and for the type checker the type is already clear. It adds so much clutter and at the end of the day I think: "Why aren't you just writing Java instead?" and that's probably where that notion originates from.
I used to be like that. When I did Java. I used to think to myself: "Oh neat! Everything has its place. interfaces, abstract classes, classes, methods, anonymous classes, ... everything fits neatly together."
That was before I learned more Python and realized: "Hey wait a moment, things that require me to write elaborate classes in Java are just a little bit of syntax in Python. For example decorators!" And slowly switched to Python.
Now it seems many Java-ers have come to Python, but without changing their mindset. Collectively they make it harder to enjoy using Python, because at workspaces they will mandate the most extreme views towards type annotations, turning Python into a Java dialect in some regards. But without the speed of Java. I have had feedback for a take-home assignment from an application process, where someone in all seriousness complained about me not using type annotations for what amounted to a single page of code(, and for using explanatory comments, when I was not given any guarantees of being able to talk with someone about the code - lol, the audacity).
Part of the problem is how people learn programming. Many people learn it at university, by using Java, and now think everything must work like Java. I mean, Java is honest about types, but it can also be annoying. Has gotten better though. But that message has not arrived yet at what I call the "Java-er mindset" when it comes to writing type annotations. In general languages or their type checkers have become quite good at inferring types.
I am not an experienced programmer, but I liked python because of the dynamic typing, but tbh no type hints are a nightmare (as I used to use python). These days I gravitate towards using type hints unless I am using an ipynb because it looks clean, but it can be a little much, it can look quite ugly. Not every usecase needs type hints is what I've learned.
A good compromise can be for example: Have your type annotations in the head of the procedure you are writing. That includes types of arguments and return type. You write it once at the head, and when you need to know you can look it up there, but you don't need to clutter the whole rest of the code. If you write well composing functions, then this will be all you ever need. If you write procedures 300 LoC, then well ... you shot yourself in the foot.
There definitely is an element of shooting oneself in ones own foot, but sometimes it seems unavoidable to me, or the effort just isn't worth it e.g. if I am using sklearn or numpy and the return types are ambiguous, then I'd have to overload each function at the head of the file or wrap it although it is clear what it does. What do you think? I think that if it's only my own code, then yes this is certainly avoidable with good composing functions.
I've come to the view that the best flow is to build a system in a dynamic language, and then - once you've got the broad strokes figured out - begin gradually typing it, where appropriate.
You definitely need to have a decent grasp of architecture to make this work - strict FP is very helpful to prevent any early spaghettification - but you ultimately get the best of both worlds this way: rapid iteration for the early stages and type safety once you develop a feel for the system you're building.
I've been doing this in Elixir in the last few months and I've really been enjoying it.
Yep, I agree with this. This is what I usually try in Python. Granted, Python is a way worse vehicle for FP than Elixir is, but I try to keep my procedures as pure functions as far as possible with not too big sacrifices in readability and performance. Most of the time a functional solution can be found, even in Python.
And maybe I am a little bit delusional thinking this, but in my experience, when you think deeply and come up with strict FP solutions, and you know what you are doing, then a lot of type issues don't arise, or are obvious to avoid. The simple fact that one thing you initialize once doesn't change over the course of its lifetime, already avoids tons of mistakes. You simply don't get this "Oh, is at that point in time that member of object x already modified, to be value y?" shit.
> Yep, I agree with this. This is what I usually try in Python. Granted, Python is a way worse vehicle for FP than Elixir is, but I try to keep my procedures as pure functions as far as possible with not too big sacrifices in readability and performance. Most of the time a functional solution can be found, even in Python.
That's really interesting. The last time I wrote any serious Python was back in the Python 2 era, so it's been a hot minute, but that Python certainly didn't feel very amenable to FP. Nice to hear that it's turned a corner. I'll keep an eye out for any use case where I could give FP-flavoured Python a spin.
> And maybe I am a little bit delusional thinking this, but in my experience, when you think deeply and come up with strict FP solutions, and you know what you are doing, then a lot of type issues don't arise, or are obvious to avoid. The simple fact that one thing you initialize once doesn't change over the course of its lifetime, already avoids tons of mistakes. You simply don't get this "Oh, is at that point in time that member of object x already modified, to be value y?" shit.
I very much agree with this, and I wish more FP evangelism focused on the many wonderful emergent properties of FP systems in production, instead of cutesy maths cleverness that turns off more people than it attracts (and those that it attracts were always going to be functional programmers to begin with).
To be clear, Python is still no good for FP, when comparing to other languages, including the aforementioned Elixir. Even though the Python ecosystem is huge, few people if any seem to spend any thought on functional data structures. And also you wouldn't be able to use them like in typical FP languages, because you don't get tail call optimization. You can only try your best in Python.
Ruby has a unified interface for select/map/reduce over all containers. They do lazy calculations if specified. You can chain expressions simply by appending them at the end without scrolling to the back of the expression. That is objectively better than lisp and python.
Sure, you can always rewrite to match that style with macros in lisp and generators in python, but they weren't meant to be used that way.
Sad thing about ruby is how they failed to do typing. I love python's typing module. I think it is the single best thing about python and I wouldn't touch python with a pole if it didn't have that module.
Lisp was absolutely meant to be used that way.
What I meant was how (op modifier *params) "homomorphism" is praised by literally everyone and their dog and it is simply worse than params.op1(modifier).op2(modifier)
What GP (probably) meant is that in Lisp you're supposed to write macros, and when you do, this "is simply not worse" than the dotted message sends:
There's a reason why just about every Lisp currently in use[1] has `->`/`->>`[2] macros: they're just so easy to write that not having them would be strange. Same with |> in OCaml - with currying, it's literally just `let (|>) x f = f x;;`. So, while it's true that Lisp (or OCaml) base language doesn't have the particular convenience, it's also true that in Lisp (and, in this case, in OCaml) you are meant to extend the language to allow such conveniences.[1] With a glorious exception of PicoLisp, because quote is all you'll ever need.
[2] And more! With `doto` you have Smalltalk/Dart cascades, with `nest` (https://github.com/ruricolist/serapeum/blob/master/REFERENCE...) you have decorators, etc.
OCaml does have the |> operator
I still like Ruby. 15+ years in, I find myself in the camp of not wanting it to change. 25 year old me would have been totally jazzed about the addition of namespaces in Ruby 3.5/4.0. 40 year old me wants namespaces to get off my Ruby lawn.
In your camp, waving a flag. I love ruby's simplicity when it comes to rapidly prototyping something, and find the wails about production type errors puzzling.
Only thing I've come near that gave me as much joy was Elixir, and I simply didn't have time to pick it up more than the most generic basics.
my mind just likes a.any? {|x| x.someCondition? }
Doesn't Ruby essentially already have namespaces, in terms of having modules? If one has proper modules, why would one ever need an alternative, weaker, concept for referring to things?
To make sure code loaded from gems doesn’t shadow the namespace of the application.
Right. Today Ruby has essentially a global namespace, where every defined module/class/const is put in the same "global dumping ground" and can override/"monkey patch" each other.
Ruby 3.5 will introduce a new language keyword "namespace" that scopes behavior to that namespace.
Fun times.This is intended for isolated code loading similar to "modules" in Python or ES6, but I am worried it will be abused badly. I'm also unsure whether they will add a "use Namespace" construct...
See here: https://bugs.ruby-lang.org/issues/21311
I find that Python is a very sad experience to read/write. I find the same for modern PHP. In contrast, Ruby's a joyful experience.
PHP is art. Ruby is outsider art.
As someone who loves Scheme (author of a Scheme exenstion for computer music, Scheme for Max), and who has done lots of Python and little Ruby, I find this odd. To me, Ruby is a much further departure from Scheme. At least in Python I can do something close to functional programming with primitives, though I don't get symbols. Ruby's "everything is an object" has always seemed to me to be even a further departure.
But then I really don't know Ruby, so happy to be told why this is wrong...
Ruby is a joy to program in. But Python is a workhorse. Ruby is Miata MX-5. Python is Toyota Corolla.
What looks like stagnation to Steen is actually [1] Matz’s remarkable foresight that provided stability and developer happiness.
Steen’s not wrong that Python evolved and Ruby moved slower, but he’s wrong to call Ruby stagnant or irrelevant. Just think what we've enjoyed in recent times: YJIT and MJIT massively improved runtime performance, ractors, the various type system efforts (RBS/Sorbet etc) that give gradual typing without cluttering the language etc.
Ruby’s priorities (ergonomics, DSLs, stability) are different[2] from Python’s (standardisation, academia/data). It’s a matter of taste and domain, not superiority.
[1] I'm stealing a point DHH made on Lex's podcast. https://www.youtube.com/watch?v=vagyIcmIGOQ
[2] I'm once again parroting DHH/Matz
My reaction to that part of the post was, “Well, it seems like Python needed to evolve while Ruby was better-designed from the beginning. That’s a failing of Python, not of Ruby.” Language stability is a good thing, which is why I prefer Clojure myself. I know enough Python and Ruby to be dangerous. I’m certainly no expert in either one. That said, Python always struck me as a bit of a hack, but people seemed to resonate with the “indentation is significant” syntax, whereas Ruby felt like it was better designed, taking “everything is an object” to its natural conclusion, similar to Smalltalk, but suffering from performance issues because that means lots of more heavyweight message dispatch.
I'm confused by this post because I think Sorbet satisfies basically all the things the author wants, and my experience with Sorbet has been really good!
It pales in comparison to what the author is talking about, editor support for instance is not good, it took them an awful lot of time to add support for linux-aarch64, it's in general rough around the edges (having to maintain various custom type files for some gems it cannot auto-generate type info for) and in general feels like a chore to use.
I feel the samw, happy that Python is improving and getting more good tooling
Yeah Python with uv and Pyright is downright tolerable. As long as you don't care at all about performance anyway (and can guarantee that you never will in future).
I'm going through the same processes as the author, after about a decade of Ruby I'm writing Typescript, Go and type-hinted Python, and I don't think I want to go back to the lack of namespaces/packages and the lack of typing.
And I've actually used Sorbet with Ruby for 2 years, but that seems like a really bad solution to this problem.
What do you think of rbs-inline?
Haven't tried that, but I think adding this from tooling will always lead to friction: "smart comments" are annoying to write.
I understand your point and it's valid. I think RBS is an interesting attempt at typed Ruby while also keeping it backwards compatible. I do however believe that having it as an separate file is not the right solution, it adds more load to the developer and ruins the joy of Ruby
RBS-inline is an interesting attempt at solving it!
Languages come and go. There was a time when there was a huge momentum behind Ruby (and Rails). It is not (sadly) the case anymore. It is a matter of traction. C'est la vie. I remember back in the 90s there was great interest in Delphi (Borland OO language) but then came Java. I don't even know if someone is still coding in Delphi. I guess Ruby will eventually go the same way.
When the metaprogramming hits just right, and you are at such a high level, the lack of typing can be excused.
Typescript is a workaround. It exists because web apps got more complex and browsers only support JavaScript. So developers need to stick to JavaScript, but they need typing, therefore TypeScript has been implemented. It’s an exception where it made sense to do so. For all other languages: if you use some dynamic language and you need typing, either wait until the language supports types natively (PHP‘s approach) or „just“ change the language. The additional complexity of an additional typing layer is huge. The complexity of TypeScript - and in general JavaScript‘s ecosystem - is incredibly huge. The biggest issue we have in software development is not that a language isn’t elegant, or you can’t write some some in 3 instead of 15 lines… the biggest problem is complexity. Developers too often forget about that. They focus on things that don’t matter. Ruby vs Python? It doesn’t make a real difference for web apps. If you want a language and ecosystem with low complexity try Go. It’s not perfect. It’s not elegant. Or PHP, which has a lot of drawbacks, but overall less complexity. I don’t say Go or PHP are the best languages out there, but you should try them to get a picture - to decide for yourself what’s important and what not.
This guy is not up to date regarding the ruby language.
Keyword arguments and destructing assignments are there.
I’m pretty sure he understands that keyword arguments are part of Ruby. But they require special syntax. He appreciates that in Python _any_ argument is a keyword argument if you (the caller) want it to be.
"I consider TypeScript to be the gold standard when it comes to type systems on top of dynamic languages."
Doubly weird, considering that TypeScript work was inspired by typed/racket, and TypeScript doesn't have a sound type system afaik and the OP's first love was Scheme.
Sinatra helped me fall in love with Ruby.
I work in Python and PHP every day though.
This reads like a love letter to programming languages. You can never truly talk about the quirks of a language without dabbling in it. And without experience with other languages - I doubt some of these quirks might even be seen as such.
Error handling for instance has always been my pet peeve. With dynamic languages like python, errors were all "exceptions". But then golang came along and decided they'd be values and that only the truly exceptional errors should be "panicked". But the if err != nil syntax became super verbose only after I learned about the Result<Ok, Err> from Rust and the matching syntax associated with it.
A lot of people are shocked when they learn about ruby's monkey patching. I for one never truly groked the packaging of python applications until uv came along to deliver an experience similar to npm.
And I agree, Typescript is the state of the art as far as static typing on top of a dynamic language is concerned. But I never considered it a programming language. More like a tool to assist developers write/manage large javascript code.
In the end, I think the true reason for returning to pythong probably had more to do with getting a python gig. I live in the part of the world where my tech stack ended up being influenced early on by the places I worked. I didn't mind learning Typescript for my first gig or improving my skills with nodejs.
In the end, every language can really get things done. And Typescript helps me pay my bills and I couldn't be more grateful. Learn to love the quirks of your language and stop comparing it unfavorably with others. To date, I've never seen a language as elegant as ruby. Nor do I find an ecosystem better than python's at data science.
I'm seeing an uptick in RoR gigs within the past week
I started my programming journey with VB6. Ruby reminds me of Visual Basic. But its never evolved past that. Python became popular because it was a scripting language that had classes and a nice built out framework of features. But it has a god awful syntax and should never be used for a large project. Also their package management is a dumpster fire.
This brings me to C#. At .NET 10 there will be another option then python. .NET 10 brings the ability to run C# script files without the need for a proj file or a main method. This will bring the full .NET Framework and NUGET eco system with it. I can't wait to replace all my python scripts with this.
There's a good interview with DHH, who is the creator of Ruby in Rails here: https://lexfridman.com/dhh-david-heinemeier-hansson-transcri... I have no skin in the game having never used Ruby, but I found his arguments interesting
I was a full-time Rubyist for a long time. I started the UK's first dedicated Ruby on Rails consultancy in 2006 before Rails was even v1.0 (IIRC the first apps I shipped back then were 0.8.6). I stuck around through the hype chain, and then started to help one employer break up a RoR monolith into micro services and adopt Java and Go (this was a mistake - we should have crafted the monolith better). I've built 4 startups as hands-on CTO with Ruby and Rails. It fed and housed me for many years.
In the last 5-7 years I've had to go in other directions. Clojure, Python, Java, even back to C and taking a look at Rust and Zig. I'm now in a role where I don't code so much, but I can see Ruby's problems - performance, the surprises, the fact it allows idiots to do idiotic things (nobody under the age of 40 should be legally allowed to monkey patch a base class or engage in meta programming).
And yet when I want to do something for me, for fun, perhaps advent of code, or a quick mock-up of something that's rolling around in my head, I reach for Ruby. Not elixir which has better runtimes, or C or Zig or Rust which has better performance, not something statically typed which leads to fewer bugs, not Python which has a huge data science community to it...
A few weeks ago I was listening to the DHH episode of the Lex Fridman podcast [0], where DHH talks about Ruby as a "luxury programming language". This matches my own experience.
When I need something to be fast, it's either because I'm dealing with a low-latency problem (and some of my side projects are very latency sensitive - 5ms can make the difference between success and failure), or because I can't afford the luxury of Ruby and Rails and the developer ergonomics.
Ruby makes things fun for the programmer. That's the point. It's beautiful to work with, even if it doesn't do all the things that all the coding books and blogs insist I should be ashamed to not have in my language.
I was slightly embarrassed to be a Ruby and RoR advocate for a while because of the brogrammer BS that emerged around both ecosystems in the 2010s. I then became very embarrassed because it wasn't as cool as a systems language like Rust or Go, or as intellectually deep as Haskell, or as hot on the ML bandwagon as Python.
But I think I don't care anymore. I'm just going to accept it for what it is, and lean into. Life's too short for "shoulds" - I'm just going to like what I like. And I like Ruby.
[0] https://www.youtube.com/watch?v=vagyIcmIGOQ
Ruby feels like a luxury manual hand saw. It fits in the hand perfectly. But it would not be my first choice for every project.
Languages like C#, Java, C++, Scala, Kotlin, and Python in 2025 feel like industrial computerized bandsaws with twenty different settings and controls. Some more complicated than others. They can be tuned to crank out a factories needs but you could spend days just fussing with a single setting that isn't right.
That being said, modern Python to me feels like the least thought out of these. It has been incrementally changed from one language to another, while being forced to keep many of the worst parts of both. To be honest, I think they should keep up the "breaking backwards compatibility" trend and make Python 4 an optionally-compiled, statically-typed language more like Go, but with more expressivity than Go.
I suppose F# is already like my ideal Python 4. It's possible to run as a script or compiled binary. It's a nice clean syntax, and the type system is a joy to use.
A valid F# program can be a single line script or dozens of configuration files. This let's the developer use it for quick and dirty work, then progressively tweak settings to run in a more industrial scale setting.
I've been on a similar journey. I was deep into rails early in my career. Then I moved on, especially liking typescript. I thought I wouldn't go back. But you don't always get the choice, a great job came up and it was a rails app. I found joy in it again - and I'm still there nearly 10 years on. Ruby feels like how OOP should be, it's so very easy to implement patterns that other languages make verbose and horrible. I'm guilty of a lot of metaprogramming, hope you forgive me, I am over 40. I think it can be an undervalued super power of the language: something isn't working or you need deeper insight, just break into the innards of any library you're using and insert logging and/or your own code.
Anyway, that said, for new personal projects I like typescript and rust. But recently I needed to stick an admin interface on such a project and rails shines there, you can get something good and secure stood up with less code and faff than anything else. In today's world of LLMs that is helpful too, rails is concise and old and has lots of open source projects to pull from, so AI breezes through it.
Likes Lisp Ruby and Typescript, interesting tastes (in a good way... nuanced)
It's the never ending "end"s that bother me about Ruby.
Clear away all those ends and the program logic pops out. Much fresher!The indent in your Ruby code is a bit weird. It should be like this
I would have done it this way instead Or if you allow me to create a separate private methodI'll leave aside the unnecessary test for parity as contrived for the example.
This is a question of style; it's possible to write ruby the way you have but with experience you definitely wouldn't.
e.g.
Why wrap the iterator in an if..else..end block? You could replace that with a one line guard statement at the top of the method. No end required.
Why use begin..rescue..end in the middle of your example? Just replace that with a single rescue at the end of the method. Again, no end statement required.
This is like nesting 20 if..else blocks in python and then complaining you don't have a monitor big enough to view it without scrolling. You just wouldn't do it that way.
My hope is that with the new ruby parser rubocop will be more agressive about automatically refactoring examples like this away.
That Python code looks like it was punched in the belly and is about to fall down on itself like a Jenga tower. Additionally, the last `else` is hard to track visually and if you make the slightest error in whitespace (which are invisible characters!), everything breaks.
To each their own. It’s because we all have different preferences that there are so many choices.
I'd love Python if it weren't for whitespace. I wanna cut/paste code into a REPL, and Python makes that difficult.
I find that the IPython REPL has downright amazing multiline support. I've always been envious of it when using GHCI. But even the standard Python REPL supports multiline paste just fine. So what exactly are you talking about? Rolling your own?
You can't copy a method from a class and then paste it in the REPL, it will complain about indentation.
Workarounds:
1) copy whole class
2) remove indentation before copying
That's only a problem in the base python interpreter. IPython has handled this well for over 20 years.
Begin/end should be optional. Some things would just be so much easier to follow, map/lambda stuff cleaner.
But the previous commenter just improved the Ruby code by adding whitespace!
It's not Ruby, it's your code... :D
class Mess
endAnd I have no idea why HN messes up my class statement... it looks good in the text field :D
Did you start it with two spaces? That's how HN does code blocks. Two spaces added before each line. The other lines look fine because they were already indented, add two spaces before all of them and it'll look fine.
I mean, that's a horrific piece of Ruby that doesn't do much, and you've not indented it properly.
Of course you can get all this down to a single line with ; demarcation.
And your `.each` could use `{ ... }` syntax, just like C or Java or... you know, everything else.
But sure, whitespace is better, or whatever it is you prefer.