Why Ruby Is More Readable Than Python
confuzeus.com
confuzeus.com
I've loved every Ruby codebase that I've written. I still consider it one of my favorite programming languages, and (in pedagogical terms) one of the best languages for learning "practical" functional programming (point-free style, blocks as intuitive closures, &c.). But when I work with others, I find that all of the things that I love are obnoxiously clever to others, and that the things they love are obnoxiously clever to me.
Python isn't immune to that kind of cleverness, but subjectively it's not as common. I chalk that up to the higher pain threshold for doing really obnoxious metaprogramming in Python, a lower community tolerance for unidiomatic interfaces, and (broadly) better QA tooling (RuboCop is great, but MyPy + isort + Black + flake8 is really hard to beat).
Not always transparent, but it should be solved with correct tooling. I think the bigger problem was LISP was far too long tied to LISP Machines, rather than being able to run independently on any hardware, which helped C and Pascal gain traction early
The "Unix workstation" of the 1980's was about the most powerful, expensive machine you could get your hands on that still qualified as a microcomputer. No wonder they got some sort of industrial Lisp to fit on it.
Unix ran on a lot less than that before that; what sort of Lisp was there for V7 Unix on a PDP-11, and what were its applications?
> The expressive power of Lisp has drawbacks. There is no such thing as a free lunch. > - The Lisp Curse
It's purely made up stuff.
We are decades past "peak Lisp".
Lisp grew in capability and complexity at a voracious pace and required expensive, powerful hardware, always at the edge of the hardware envelope.
Then microcomputers happened, featuring the power and speed of big iron machines from 15-20 years prior.
Almost no software survived the transition from big iron to microcomputers. Pretty much no operating system, and few programming languages or applications---everything was "rebooted". Microcomputers went through their own cultural evolution, with entirely new software.
Customers were buying microcomputers, including lots of new customers that didn't have computers before, and you couldn't sell them software that wouldn't run (or fit) onto those.
Simple as that; nothing to do with macros.
(Unix jumped to microcomputers because it was a lean, resource-efficient minicomputer operating system; the lag between micros becoming as powerful as minis was not as great (decade or so) and basically dove tailed with the Unix timeline. Unix was ready just as micros were becoming ready.)
If it was about macros, we wouldn't be talking about Lisp; it would be entirely dead. HackerNews would be in written in Paul Graham's small dialect of PL/I or something, which would still be here due to not confusing anyone with macros.
I spent a few years doing ruby in pair programming contexts and never had that problem. It was a great experience for me. I guess we didn't have that problem because pairing both gives you immediate feedback on "too clever" and because it's much easier to understand a clever thing if you do it with somebody rather than coming across it later in the code.
I never could quite articulate why Python doesn’t resonate with me without hyperbole or snark or both.
But that’s it. Python’s design includes inducing pain deliberately with the rational that I deserve it.
Don’t misunderstand me. I think Python is useful. It is just that I don’t find it enjoyable and I inflict enough pain on myself with my code.
If you're going to quote it, don't you think you should quote the whole thing? The whole point of making the pain threshold higher for certain kinds of metaprogramming painful and obvious.
I think that is a good thing. You can use it when you need to, but when it comes up in a code review, it's extremely obvious that you are reaching for a powerful tool which can (and in my experience, almost always) impedes ease of code readability and reasoning about behavior. These things are toxic to growing organizations with growing teams.
It sounds a reach to misconstrue that the design "includes inducing pain deliberately with the rational that I deserve it" -- it's not that you deserve it, per sé, but you may not be using it for the use case it was intended for.
My use cases have spanned decades of experience across dozens of organizations serving billions of dollars of industrial strength pressure. Having used other languages (including Ruby, Java, Go, Scala, Cxx), accounting for tradeoffs between ecosystem maturity and language flexibility, it remains my overall top choice for getting work done and building durable, maintainable codebases.
That someone thinks it’s good for me doesn’t justify the deliberate application of pain to me.
I don’t mind that “it works on your machine.”
My distaste for Python’s philosophy is my distaste.
I simply think my life is richer without aspirations to be Pythonic.
And now I can put my finger on why.
Thanks.
In the former case, I think that having a "distaste for it[Python]'s philosophy" where one's life is "richer without aspirations to be Pythonic" is pretty reasonable.
In the latter case, there isn't as much room for that. Instead, the more practical considerations I mentioned predominate: the question is how to get the most mileage out of language in a large organization to accomplish large, complex business goals with technology in a durable way.
It's not to say that one or the other is more important (I think there is plenty of space in this world for both), but I would say that the vast majority of ecosystem space around programming languages is and very likely always will be taken up by the latter use case. If you're curious about why things are the way they are (which you may not be, or which you may simply not care about, or which may simply not apply to you), then I think it's worth considering.
Most poetry is like https://hitchhikers.fandom.com/wiki/Vogon_poetry.
And the worst is that I'm unable to do poetry.
---
I'm enthusiast about learning about languages, and enjoy everything I see about APL, Ruby, Scheme, Forth, Haskell, etc. I actually LIKE the concepts.
Is kind of relaxing take a look around the basic tutorial or read what the "good" poets say about that.
But my instinct tell me it will be torture to live like a poet.
So, for actual work, is much better (for me) all languages that have restriction and strict ideas about stuff.
- Python FORCE indentation? SOLD!
- Pascal DEMAND declare all before use? GIVE ME MORE!
- Rust SMASH YOUR HEAD at every keystroke? SHUT UP, TAKE MY MONEY!
Ruby is my favourite language!
Am I a haiku
has an extra syllable,
but that's fine by me :)
In practice, Ruby code isn’t. Once someone starts doing advanced meta programming, get the shovel, because you’re going to kill whoever wrote it and need to bury the body.
Rails largely gets away with this by having opinionated design, good documentation, and a large user base who have already written answers to your questions.
Yes. And even just _finding_ relevant bits of code is difficult and painful, because you can't simply `grep` for the names of these metaprogrammed methods. They're simply not in the codebase.
Metaprogramming in application code is one of those trends I'm glad is behind us. Might look cool and work great initially, but a painful refactor is near inevitable once there's been some turnover on the team and the problem space shifts in a way that invalidates the underlying assumptions.
Library code is another matter though.
At the end of the day coding is hard. I’d rather have nice DSLs to learn than verbose spaghetti to read.
Metaprogramming in a pull request at work? Nope. I'm going to smack some knuckles with a ruler. :)
This is why Elixir makes me hate life sometimes. They took a language like Erlang where it seemed almost impossible to write an unreadable program even when it was doing very sophisticated things, and Rubied it up.
Just goes to show how subjective it all is!
I might also add, if you create getters for an instance variable then you don't need to use the @, except in the getter itself (and you don't even need to do that as there is the `attr_reader` helper for that).
Python is far more readable and comprehensible than Ruby.
Haven't even bothered to read TFA because it's just weird flamewar bait.
…
“I hope to see Ruby help every programmer in the world to be productive, and to enjoy programming, and to be happy. That is the primary purpose of Ruby language.”
- Yukihiro Matsimuto
> In Python, you will never do post.__str__ for example. Instead, you’ll do str(post)
No you dont. You do the same thing you did with `puts post` in your Ruby example: `print(post)`, which will call `__str__()` method.
> In Python, it’s easy to accidentally write to the count attribute — which can break your program.
To address writing into attribute directly - you can use @property decorator, which will stop you into writing into given attribute, unless you add a setter.
However the main problem in this fabricated example wouldnt be that you can break your program by accidentally writing into an attribute. I'm not sure how common is it in Ruby, but mixing the instance variables together with class variables this way seems like a bad design, and that is what will break your program.
Regarding the Django example - ok that is far from perfect, but comparing a framework which maintains backward compatibility for years with code you write from scratch without worrying about anything is barely a fair comparison.
Ruby is clever, it can be beautiful, but I've never seen a good codebase using it grow well without enforcing strictly opinionated ways of writing it to ensure maintainability. Which breaks a lot of the expectations of some Ruby developers that chose the language because they like to write it in their own way.
Yeah, this is one of the major disadvantages of Ruby. Every Ruby shop of any size has its own idioms and internal code style, which means a lot of unnecessary overhead when onboarding new people. Even experienced devs need to take some time to feel out their team's norms.
The expressiveness also lends itself to bike shedding - I've seen many a PR comment questioning why a dev chose method A versus method B, where method B is an alias of method A. Probably an hour of staff time spent on something that has zero impact on functionality, maintainability, or performance.
But a team cannot have its own opinions with Python. They're already built in.
I believe every good maintainable software needs an enforcing strictly opinionated ways of writing it to ensure maintainability.
Language need be general to be useful. Any language can create utterly unmaintainable code. If you shift "opinionated" into the language, it is less easy to write unmaintainable code, but it is also more difficult to write fitting maintainable code because that "opinionated" opinion may not best fit (and usually can't best fit) a specific application, a specific team, and a specific set of experience. On the other hand, if we shift "opinionated" to the programmer, the language inevitably need add more meta-programming ability or simply more ways to do the same/similar thing. The code can easily become unmaintainable under less experienced programmers who are not opinionated and aren't disciplined.
The current programming culture expect programmers to be commodities, thus favors opinionated languages and idiomatic code. I can easily imagine a shift in culture that favors experienced and opinionated and disciplined programmers, where different set of languages will be favored.
Today's software, once grow to certain size, are all quite unmaintainable.
Still applicable I think.
Well I thought this was rather silly and counterintuitive of Python, and said so in some forum or other, whereupon i discovered the very passionate views of folks who have come down hard on one side or other of this debate.
So, lesson learned: keep one's opinions on such matters to oneself, as one man's banquet is another's poison. In which vein, it would be splendid if this comment did not occasion another screed about the manifest glories of __len__ to enlighten us heathens.
This makes it sound like you where trying to learn the language by typing in random stuff and just hoping something runs.
Yes every language will leave you scared if your path to learning it doesn’t even involve reading a single page about its most basic functioning.
Firstly, not 'scared', 'scarred' - merely a bit of harmless hyperbole to indicate a briefly frustrating situation.
Secondly, this is not monkeys-at-typewriters, merely an expectation that Python would conform to any other object-based language and have a public method on string to report its length. This should be a discoverable, not an RTFM case.
If I say that halloumi is better than tofu, do you not assume I'm talking about my personal opinion? Do I need to make it painfully clear that it's my subjective opinion?
Taste, as much as readability, are subjective so that can be assumed.
Also, if you read the post, you'll have noticed the author is constantly saying "I find this more readable", and engaging the reader with "what do you think?" questions on each point made, which makes it very obvious that they're presenting a personal, subjective view.
Yes. Because a sentence written in the Arial font, as much as I might find that font distasteful, is more readable than the same sentence written by a Captcha-generator.
A bubble-sort written in Golang is easier to read than the same bubble-sort written in Brainfuck.
Yes, there are exceptions to the rule (I imagine someone who knows Brainfuck, but doesn't know Golang for example), but the rule is very clear in both cases. The clickbait flamewar-inducing headline is saying, essentially, Ruby is the former in the examples I gave, and Python the latter.
We're not junior-high students trying to one-up one-another with cute gotchas. We're technical professionals. We should expect higher standards of communication skills from other technical professionals.
If you mean "I have a useless opinion that no-one should care about," then state it. If you mean "I have statistical evidence that most technical professionals will be able to comprehend programs written in Ruby better than the same programs written in Python," then state it. But don't expect a good reaction when you say the former, while tricking us into thinking you were saying the latter.
Even the "obvious" counter-example, lying, works because every sentence said by a person is their opinion. You couldn't lie if it wasn't.
I don't. People will care about. Not everybody but some will, and that's fine.
> We should expect higher standards of communication skills from other technical professionals.
Reading comprehension is just as important as writing. I expect my peers to know that an opinion is subjective without me needing to say so.
For instance, I would have expected you to not gloss over my last paragraph on the previous post, just as much as I would have expected you to understand, by reading the article, that everything written there is the author's opinion but I guess that wouldn't have been such a good straw man.
Go through a typical college writing class and the teacher will direct you to delete phrases like “I think” and “I believe” from your writing.
I do wonder how this style of writing has impacted the psychology of argument, I imagine there are others who read this style of writing and assume it is written in stone or as an objective statement, and not a subjective one. This may just be human nature.
I find it makes for less combative discussions. So I like it.
Eternal September gets worse when their teachers are teaching them bad habits from the get-go.
In your opinion eternal September....
Assuming "click bait", "trolling" and "dramatically" are all either explicitly defined or implicitly defined by consensus or what have you, this would not be a matter of opinion but of fact, so stating "I think" would either be incorrect or would be an admission of uncertainty about whether the fact is true. If he is correct and honest, he has no reason to add the phrase. If he is dishonest or incorrect, he has reason not to add the phrase.
Additionally, in the context of the full sentence, it would imply that the subject pronoun "this" is causing his thoughts rather than causing the object of his thoughts, but I know what you mean. I think.
I'm all for pieces clearly from a personal perspective. There one can drop the redundant "I think" bits. But often dropping the qualifier turns it from a statement about one person to a universal statement, as with the title of the article. For me this erases the many interesting possibilities in between.
For example, a post title something like, "Which programmers find Ruby more readable than Python?" is way more interesting. The universal is false, but I'm sure it's true for some people and false for others. An exploration of that could lead to interesting ways to think about languages. E.g., is it better for novice programmers? For programmers from non-engineering backgrounds? For programmers with difference cognitive styles or capabilities?
What if you don't care about analyzing the psychologies of programmers, and just want to say why one thing is more readable than another thing?
In a world where people (in general) are capable of distinguishing fact from opinion (most of the time), you can omit the "I think", "I believe", "I feel" crap. In a world where people are bad at distinguishing fact from opinion, you can't rely on the "I think", "I believe", "I feel" crap because writers wouldn't be able to use them correctly.
And the post body reads: "Because that's what they're used to." Fin. Roll credits.
Personally, I find Python more readable, because I know Python better than Ruby.
I don't think there's anything inherently more discoverable about @@ to denote a class field. Python instance fields are all prefaced with self, so a bit of cursory Python learning makes it clear.
But yes it feels weird that you can't declare them upfront. (And it gets weirder when using libs like Pydantic where class level fields are used to describe instance fields in (de)serialised objects.)
IMO, Java's static keyword is far more discoverable because it's far easier to google.
As I discovered when trying to figure out what an @ meant in a TS import statement.
But, I'm not writing a blog post about why Java is more readable than Ruby, because that's just, like, my opinion man.
Often the goal in writing an essay is to share a personal view or experience, and sometimes to get others to share their thoughts on a topic. For example, if you did write an essay on why Java’s static is better than Ruby’s @, it might lead others to engage with you, share their thoughts, and help you learn more or modulate your own viewpoint as a result.
Montaigne, the oft-touted father of the essay as a form of literature, in fact explicitly wrote essays as a means of self-reflection and self-exploration.
This isn’t some technical paper coming from a research lab or standards body, it’s someone interested in a particular topic sharing their thoughts on that topic, potentially influencing others and potentially being influenced themselves.
The "that's just your opinion, man" thing is the only thing I'd like to see as a universally ban-worthy offense on the internet. It's an entirely empty statement that can be made about anyone who said anything, is always a bitter defense against somebody who took the risk of putting themself out there, and shouldn't be seen as anything but a troll.
And I'm fully aware that this is my opinion, which is why I said it and not someone else.
The author doesn’t do any of this, in fact the author is barely even arguing a point. It’s pretty clearly an opinion piece. Human beings have been writing like this and differentiating between fact and opinion for centuries. Demanding that all writing be watered down to hedge-laden statements and statistics would land us in a very boring world indeed. People can read, judge, and think for themselves. We don’t all approach literature like computers reading instructions.
Also, those ruby examples aren't readable, I have no idea what your sigils and double sigils are doing beyond giving me Perl flashbacks.
You can do…
module Foo
extend self
def bar
“Bar!”
end
end
Foo.bar #=> “Bar!”
And you can include that module in other modules. No classes required.This really nice when the one function you need is giantlib.helpers.foobar.convert_baz_to_qux - it’s both less bytes (especially if you are calling it a lot) and more importantly to the next reader of the code it means that c_b_to_q is the only thing you’re using in giantlib
I tend to go with static methods on modules.
{
title: “a grumpy programmer’s rant about objects”,
body: “Wait, hear me out y’all…”,
published: true
}
Both the Python and ruby examples have no post-ID, so mutating in-place is an extra-bad idea. It should not be the default.Why does the static count variable exist? These things are all stored in some list (memory, db, whatever, perhaps multiple places) and we can count any subset of those that fits our purpose. What happens when I’m asked to add draft posts? Should I only update the count only if published == true? It depends on whether I’m looking at “number of blog posts” as a user or “my drafts” as an author.
Why does the toString exist? Nobody uses those in real app code for end-users, they are a convenience for developers. Use a better REPL/debugging environment that can handle pretty-printing data. If you need more customization, use a function.
Please, just use plain data wherever possible. Please use languages that support and encourage using plain data. Please push mutation to the edges of the system, and please carefully control the in-place updates. The computers in our pockets are more powerful than anything that existed on earth before 199x, yet the industry is still acting like we’re running them on 8086 microprocessors with 640k RAM.
Let’s grow up and think more deeply about building robust systems which can evolve with changing needs.
EDIT: Changed published to true, because this is basically that.
To migrate an API - that's what I said.
There’s no change in interface needed in the ruby example if you decide that you want to replace that with fully-written getter/setter methods like the post shows; attr_accessor generates exactly those same signatures for you.
>>> class Foo:
... @property
... def bar(self):
... return "hello from a property"
... @bar.setter
... def bar(self, value):
... print(f"tried to replace bar with {value}")
>>> Foo().bar
'hello from a property'
>>> Foo().bar = "asdf"
tried to replace bar with asdf @foo
def bar():
...
Is just syntactic sugar for: def bar():
...
bar = foo(bar)No that’s instance variables.
> Which part do you find ugly?
In Ruby to modify a method you just call a method. I guess I think a second, redundant, way to call methods is not elegant.
This is because there’s not a good way to pass two delayed computations - two blocks. Looping constructs were replaced by method variants.
> also blocks are not objects for some reason
Yeah they’re reifiable but not sure why not reified by default.
I don't know that much about Ruby's implementation but I think both the if else and blocks not being objects are that way to prevent Ruby from being even slower. My understanding is if you turn a block into a proc it gets a lot slower.
But I could not, for the life of me, remember which symbol means private, which meant protected, which meant public. There was no affordance to the symbols, and I have never seen any other OOP language that does that.
And then I moved on to Python and Django, and found the lack of real private/protected/public in the Python world utterly liberating. The more I programmed in Python, the more I appreciated “we’re all consenting adults”.
The more I thought about it, the less reason I find in trying hide anything via protected/private. For example, if I understand correctly, in .NET/Java, anybody with enough determination (and runtime permission) can use your protected/private stuff via reflection anyway.
This interesting article uses dark red for operators (".", "=", "<", etc.). This is one of the worst choices. On a dark background, please do not use dark color.
Condiser the following task: take /proc/kallsyms in Linux, that lists symbols in kernel, in an '<address> <type> <name>' format, like:
0000000000000000 A fixed_percpu_data
0000000000000000 A __per_cpu_start
0000000000001000 A cpu_debug_store
0000000000002000 A irq_stack_backing_store
0000000000006000 A cpu_tss_rw
000000000000b000 A gdt_page
...
let's make stats on it -- how much of each type is present? Wanna get the result ordered by number of occurrence.In Ruby:
# ruby -rset -e '$<.readlines.to_set.classify { |l| l.split[1] }.transform_values(&:size).sort_by { |_,v| v }.to_h.tap { pp _1 }' /proc/kallsyms
{"V"=>1,
"w"=>2,
"a"=>14,
"R"=>98,
"W"=>154,
"A"=>328,
"B"=>655,
"D"=>2910,
"b"=>3097,
"T"=>22289,
"d"=>34424,
"r"=>49904,
"t"=>55346}
Of this method call chain, first and last are impure (as they do I/O), the intermediate ones are pure.In Python you'll have to grind through it procedurally. (Unless you use reach out to some advanced libs... https://gist.github.com/richardbann/5b363096de6b3de2e8178cce...)
class BlogPost
class << self
attr_accessor :count
end
self.count = 0
attr_accessor :title, :body
def initialize(title, body)
@title = title
@body = body
@published = false
BlogPost.count += 1
end
def publish!
@published = true
end
def to_s = @title
def count = self.class.count
end
This avoids needing to understand what `@@count` means, and lets you use the same syntax as in python: `BlogPost.count += 1`.Or, if I were in a codebase using static type annotations with Sorbet, I'd do something like this[0]. To each their own, write code in the language and style that makes the most sense to you.
[0] https://sorbet.run/#%23%20typed%3A%20true%0Aclass%20Module%3...
Jez!
If you plan to subclass BlogPost, the translation is not identical, as the alternate implementation would not keep track of a global count of posts across all subclasses, only of `BlogPost` instances exactly.
Gues what? They aren’t required in python either
> While both languages are much easier to read than say, PHP or Java, [...blah blah blah...]
Which leads me to wonder: Have there been any serious studies of the readability of programming language syntactic and semantic conventions? For instance, a good friend of mine (whose day job is working as a software consultant, and whom I imagine would disagree with the author's assertion) finds it extremely difficult to understand python code in particular due to his dyslexia, and I wonder how common such problems are, both as a % of people and % of programming languages.
For Java, Rust, etc it's a bit different. I would say that's up to taste. Using "public static" is very verbose but more obvious than @ or _ prefixes.
Admittedly, it's hard to tease this apart from the standard library and communal conventions of any given language: Java occasionally devolving into an endless sequence of ProducerFactoryFactory.newProducerFactory().initializeProducerBuilder(new ProducerBuilder())... certainly impacts its readability in a practical sense, just as the Haskell community's perhaps ill-advised love of point-free programming and highest-order polymorphism doesn't always do it favors on the readability front. Those sort of details seem like they'd really complicate a rigorous and systematic study, but on the other hand, lacking a good methodology has never stopped _other_ supposed quantitative studies of programming languages as they relate to e.g. "developer productivity" and "defect rates," so who's to say...
in those examples, python looks more readable to me (and easier to write) because it is more concise. the less code that i have to scroll through, the more readable it is. (to a degree. APL is compact but not concise because it uses short symbols for things that would be readable words in other languages)
getters and setters are boilerplate. if you want them, you can use them in python too. common operations should be easy, uncommon operations should be possible. python is doing that. ruby in this case forces me to treat a common operation the same as an uncommon one. personally, i only want getters or setters if they do something beyond plain return and assignment of the value, like a range or type check for example. that way, when they actually matter, they stand out in the code. the same goes for access to class variables.
i get it, there is some beauty in having every class look the same. you can just write it all down without much thinking. and when you need a custom setter, it's already there and easy to modify, whereas in python i may have to do some extra work, add the setter, and make sure its use is enforced. it's a trade-off.
+() vs __add__() to define a + operator? sure, +() looks more elegant, but in the end the code is the same, __add__() stands out well enough, and while i too would prefer +(), it's not something i'd bother complaining about. there are other things that are more irritating.
and finally, single inheritance vs multiple inheritance. an age-old discussion with good arguments on both sides. pick your preference.
I'm thinking in terms of avoiding back-tracking in your syntax.
E.g.: regices are hard as shit to read, and I don't think that back-tracking helps
(ab + c)*
Or *+(ab, c)
The first says ab, no - it's the union of ab and something, something is c, no - it's one or more of those.The second says one-or-more of the union of (ab) and (c).
Why haven't languages pushed that way?
Please no. It's bad enough that people insist on using foreign plurals in English, let's not pretend "regex" is some latin word (it's just REGular EXpression).
I'm a fucking programmer (at very least I'm a dude commenting on HN) - I know what regex is short for.
I'm not a Ruby programmer but I wrote this post while learning Ruby and after having read through the entire Sinatra code base. I was surprised at how much easier Ruby code is to read even though I've been a Python programmer for the past 6 years.
I have noticed that this extends to Ruby's approach to bundler, and Python's approach to virtualenv/et al.
Knowing this made me less irritated at Python.
Did it miss the data science/ML boat?
Font ligatures are bad enough