Ruby vs. Python comes down to the for loop (2021)
softwaredoug.com
softwaredoug.com
In fact, Smalltalk takes this much further, such that basically all flow control (including if-then-else) is handled as message sends (e.g. if-then is just a message sent to the Boolean object taking a block as it's argument).
The downside is the syntax can feel a tad clunky. The upside is incredibly simple and consistent language grammar, while making it trivial to create new flow control mechanisms since the language has all the tools baked in (primarily first class blocks).
I hope you don't mean Ruby (I haven't investigated Smalltalk grammar). Ruby grammar is atrocious due to string interpolation stuff. Nothing to do with handling loops or conditionals, but still... Ruby is not at all an example of a language with good grammar.
Unfortunately, it's surprisingly rare for popular languages to have good grammar. If you look at it from up close, there are lots and lots of really bad decisions in virtually any language in common use today. I think, this is just not a high-priority concern for most users, but still...
But, I believe, size is a very good heuristic (i.e. the number of rules, the number of variables in rules, the number of branches in rules).
Another valuable metric to optimize is entropy. I.e. rules that look very similar aren't very good rules.
Expressiveness: how long does the program have to be to capture a useful concept.
Things like these obviously need a lot of counting and coming up with some kinds of constants, hopefully justified by experiments... That's a lot of work that will also require a lot of resources to do. But this is not to say that it is unknowable or that a programmer cannot develop an intuition which allows for rough assessment of language grammar.
So, I'm sorry, I don't have a good answer... I only offer one based on my intuition and limited experience of dealing with various language grammars.
https://richardeng.medium.com/syntax-on-a-post-card-cb6d85fa...
Once you implement `each`, `include Enumerable` is all it takes to get the full set of collection methods (including `max`/`min` etc, if the entries define `<=>`).
The entire article largely read like "Python developer learns Ruby", and on top of that now fairly dated Ruby, though I'll make some concessions for him wanting to show a close parallel to the Python. I wish he'd signposted more clearly that these are examples, though, because as it stands it implies this is how to do things, while it really is not.
E.g. his "Stuff" example can be reduced to:
class Stuff
def initialize
@a_list = [1, 2, 3, 4]
end
# The ellipses here are part of the Ruby code for 'forward
# all the arguments, including the a block if passed'
def each(...) = @a_list.each(...)
include Enumerable
end
Stuff.new.each {|item| puts item }
puts Stuff.new.map {|item| item}
puts Stuff.new.select{|item| item.even?}
One could argue about my use of "..." and endless def, but one certainly would not typically implement each when forwarding to an Array by using a for loop to iterate over it other than to make it relatable to Python developers...And, more controversially perhaps, the last three lines can be reduced to:
Stuff.new.each { puts _1 }
puts Stuff.new.map.to_a
puts Stuff.new.select(&:even?)
The first one is one that will cause arguments. The second just showcases that map is pointless here other than as an example - map here effectively just a slow way of duplicating the array, but notably first when forcible evaluated as map without a block will return an Enumerator, and "&:even?" is fairly idiomatic for "call to_proc on this symbol and apply it to the argument", but some might still not be familiar with it.The Python example is similarly unidiomatic, reduceable, and flawed for the sake of simplicity (the example doesn't allow multiple independent Stuff iterators), so it's not like Ruby is really at a disadvantage in the article. The point of the example is to illustrate the mechanics of custom iteration (which forwarding to the encapsulated implementation wouldn't accomplish, for either language) while keeping the rest of the Stuff definition as simple as possible. But yes, a brief mention of including Enumerable wouldn't hurt.
One rant though: Am I the only one being overwhelmed by so many "end" delimiters in Ruby? Feels like visual noise. end end end end...
> I have reverse engineered secret security algorithms used by the CIA and can break any message they encrypt. As proof, here is the last few lines of an implementation of their encryption function in Lisp
))
)))
)))
))))Apparently joke-killers aren't a new thing :-)
In that regard, separating `sorted` (immutable, returns a new sorted list) and `sort` (mutates, returns nothing) helps a lot in terms of preventing subtle mistakes.
I don't know how Ruby handles this case though.
> The bang (!) does not mean “destructive” nor lack of it mean non destructive either. The bang sign means “the bang version is more dangerous than its non bang counterpart; handle with care”. Since Ruby has a lot of “destructive” methods, if bang signs follow your opinion, every Ruby program would be full of bangs, thus ugly.
(from https://www.ruby-forum.com/t/conventions-in-ruby-and-the-pri..., a quote from 2009)
I'll argue that `Process.exit!` does modify the state of the program in place (which isn't the typical thing one thinks of when thinking of values). Without the bang, it's just a function call that throws an error, which can be caught and handled, so the logical state machine is unchanged. With the bang, it replaces the set of states the current program can be in to a single exit node.
So you'd have
module A
module B
class C
ennnd
Fortunately, I am no longer suffering from whatever fever-induced hallucination brought on that particular deviation from good taste and decency. sorted_list = sorted(my_list)
sorted() also has a reverse option and a key option to specify the keys to sort by.And the sort is guaranteed to be stable.
This is (one of) my biggest gripes with Python. It's utterly inconsistent with its design, and library designers have taken that to mean they also can do anything they want, leaving NumPy vs Pandas to have completely different class vs object stylings for instance. I reach for Python these days only when there's no other option because I'm 100% sure I'll spend 50% more time in the debugger than using Ruby for a similar problem.
sorted_list: list = my_list.sort()
will raise a red flag with a type checker.
Ruby vs. Python comes down to the for loop - https://news.ycombinator.com/item?id=29199810 - Nov 2021 (302 comments)
I think we're dealing with someone who had limited experience with both Python and Ruby. This article is somehow getting more attention then it merits.
Edit: much more interesting would be the contrast to list comprehensions in Python and the somewhat associated crippled lambdas.
Another interesting one is Ruby's private methods being not accessible by other instances of the same class whereas Python's are (to be bizarrely) accessible by instances of the same class (like I can use your heart because we are both human?). I always justified this difference in my mind with Ruby object despite being of the same class potentially having different methods due to metaprogramming at runtime. But it's probably just due to the languages' respective ancestors.
Not sure what you mean here; Python doesn't really have private methods.
This is very rarely used to try to achieve privacy in practice, not least because it's extremely easy to get around.
IMO the goal of declaring something private is to make clear what you consider part of your object's public API and it's safe for others to call. It's mainly to protect callers from shooting themselves in the foot. Private provides no security. Your consumer will know better the actual situation they are in and if it's worthwhile taking the risk that your API might break them in the future or to write additional tests since they are doing something you as the author advise against. Of course it can also help enforce good design within your project. IMO it should be easy to bypass private constraints when a user makes that educated choice
class Foo:
__bar = 1
f = Foo()
assert(f._Foo_bar == 1)Have you never used C#, C++, or Java?
Unfortunately.
The problems with Ruby seem to be:
* No static typing (I think there is Sorbet but apparently it's not very good and Gitlab doesn't use it).
* The lack of "syntax" makes it hard to grep for things. For example you can't find where `foo` is called by searching for `foo(` or `.foo` like you can in Python.
* It seems to encourage highly dynamic code where even identifiers are dynamically created, so often you'll find an identifier, and try to search for its definition but get zero results.
Maybe it's elegant and nice to write, but it's definitely awful to read.
I've had to take care of 3 large ruby codebases at different companies and this is what kills ruby for me.
A lot of ruby programmers think they are being clever when writing crazy dynamic ruby code, but they are only creating technical debt.
Years later when they have left and the "context knowledge " is gone from the team, the Ruby code is a huge mess of "magical" code.
And rails with its "implicit" functionality depending on method name. A lot of Ruby feels like "magical" to me (in that, things work because of some hidden implicit readmson).
I prefer code that is explicit, in your face. You can quickly see what it does and how it does it. Principle of least surprise and "don't make me think".
As a language it's pretty, I like to say that Ruby is really object oriented while python is a mix of stuff (why len(x) instead of x.len??)
If I can’t find def method or def self.method, your code is not passing my review.
I wouldn't say it encourages it explicitly but it makes it far too easy. There is usually no need for highly dynamic code in code that is not part of a library.
> Also, why grep in the age of lsp?
I'd rather not! But Gitlab doesn't use static type hints so I have no choice.
Sorbet is great, just not with generics yet. And if you use it the LSP will allow for finding references to method calls. If Gitlab doesn’t use it they’re missing out. Maybe they are too busy writing about how great their culture is to worry about such things…
If you don’t want dynamic identifiers, configure rubocop to prohibit it and enforce it through CI builds.
I guess the only reason this doesn't happen the same way in Python is because developers are told to repeat "explicit is better than implicit" 100 times before joining the community.
I can't find any single reason why the language would discourage it.
I wish we wouldn't try to simplify the wildly different philosophies of language into a single "thing". From imports, loops, calls, sub-classing, chaining, multi-lines, blocks - there are just so many differences that materially matter.
That said, regarding the loop itself, I agree with the article. I come from not-C background and looping in Ruby has always been a pleasure because it feels more natural especially once you include chaining.
- (Desktop) GUI
- Natural language processing
I would really like to learn Ruby, but I can only justify the effort if I can use its ecosystems for some private projects, and I often have been burned by languages not offering too much in those two areas. And speed-wise, as I understand, Ruby is the same ball-park as Python?
I'd say Ruby is the programming language I want to program in but Rails pays the bills.
For NLP you will have less choice than for Python, but worst case you can bridge to Python code.
GUI kind of works best with whatever language the platform for the GUI toolkit wants you to use, but a lot of the time C++ will be that language, competing with JavaScript. Every other language, almost always will end up having bindings, or some other sort of outsourcing mechanism to connect its runtime to either C++ or JavaScript. If you want to just deal with one language when working on a GUI project, it's best to just go with the one the target platform wants you to use.
Unfortunately the ecosystem suffers from rot because it was a language for trend-followers at one point. But it is still a better developer experience than just about everything else I’ve worked with.
I’d say Ruby is a joy to use, there is this gem (package) for gui development called glimmer, you shoukd check it out.
Though, if you are going to focus on the scientific field, I think Python is more suitable.
Here is a extensive list about NLP in ruby: https://github.com/arbox/nlp-with-ruby.
If you want a fast running and fast starting GUI you should take a look at GraalVM from oracle and its Ruby implementation called TruffleRuby, it translates Ruby code to native code and optimizes C and Ruby code at compile and runtime to make it faster: https://www.graalvm.org/ruby/
Not saying they don't work, but if you click on a few from that last you'll mostly see last commit >5 years ago - in my experience.
While Ruby community has pursuded many implementations in regards to JIT, the reference implementation even has two currently, on the Python side outside PyPy, nothing else has actually got any community support.
Only now thanks to the pressure of Python being the "2nd coming of Lisp for AI", but without its native code generation, there is some real pressure that actually writing C,C++,Fortran and calling it "Python" isn't that practical and a JIT on CPython would be welcomed.
On the other hand, those native libraries can be equally called from Ruby.
Without a doubt, choosing python was the correct move.
I can't imagine with the current trajectory, Ruby will be better.
Paraphrasing Guy Steele [1]:
> It's important that when you design a language that does a familiar thing, that you do the familiar thing exactly... it's criminal that you can write 1/2 and get values nowhere near one-half.
Python
>>> 1/2
0.5
Ruby
irb(main):001> 1/2
0
To a novice programmer, ruby's result makes no sense, and introduces an incredible degree of doubt into the mind of the user. Sure, they could explore why it gives that result, but if that's not helping them get toward solving the problem they're using the programming language for in the first place, then it could seem like an indulgent tangent.
And this isn't the reason Python succeeded. Nor is this the reason for the particular change.
The reason Python succeeded with data-science is NumPy and related group of libraries. They happened to be the first to offer easy access to R-like features of other statistically-flavored languages in an all-purpose language. I.e. it makes it easy to combine general-purpose code with statistics-specific code, and once it accumulated critical mass, the process became self-sustaining and alternatives died off quickly.
The reason for most of the changes that happened to Python in the last fifteen or so years is design driven by fashion. Which means the majority decides what to do with the language. Which also means that Python is made to look more and more like other mainstream languages (eg. Java, JavaScript, C++...) So, a lot of changes, this one included were made out of subconscious fear of non-conformity.
Anyway. Maybe my original comment was poorly phrased, but I was not implying that Python succeeded because of this form of catering. Rather, the designers took note of Python becoming popular in that field and made changes (see also the matrix multiplication operator @) that accommodate those users rather than the more "typical" CompSci crowd.
But, none of that is really relevant. Both operations are useful and common in statistics. Which one is more common will depend on your domain.
> the designers took note of Python [...] made changes
That's putting too much faith in designers of Python. Even calling these people "designers" is giving them too much credit. By their own admission they don't have any sort of vision or strategy for how to deal with the language, they just add random stuff and see if a lot of people complain or thank them.
In other words, matrix multiplication operator is there not because there was some kind of intention or design on the part of the small group of people who are responsible for releasing the language, it was more of a "genetic algorithm" kind of thing: change - iterate - see if change optimizes some metric - repeat.
It's not wrong that 0 is the behaviour of C/Ruby, it's just that 1/2=0.5 is the expected result of much of Python's target audience.
Also anyone who's done any bit of programming should know that numbers as represented by a computer are discrete, while mathematics deal with symbolic relations which might require infinite amount of data to numerically represent.
Binary floating-point can't even represent 0.1.
Haskell is an academic experiment and is mostly esoteric in the industry. It has however impacted a number of other languages that do get some use.
Pascal is way past its prime and no major company is giving it significant support anymore.
I wouldn't really group Pascal and Haskell together (unless we're playing rhyming games).
One of those had serious market penetration, a whole industry behind it and was one of the dominant choices for applications languages ... for maybe two full decades (including Turbo Pascal all the way through to Delphi).
I mean, right now, Delphi is still orders of magnitudes more popular and in use than Haskell, which is used by .... pandoc, maybe?
Java certainly doesn't: https://godbolt.org/z/b847KeaGv
There's a good reason for this. Computer numbers are not the same as numbers in mathematics. Floating point numbers are not real numbers and ints are not natural numbers.
Neither Java nor CL were intended for the novice; I'm sure Mr. Steele had Scheme in mind.
> (print (format t "1 divided by 2 is ~a and its type is ~a" (/ 1 2) (type-of (/ 1 2))))
> 1 divided by 2 is 1/2 and its type is RATIO
I don't think there is a simple solution. Numbers are harder than they seem and you just need to know what happens in the particular language you are programming in.
When one has to jump hoops of the language - being the user for the whims of the language and not the language for the user - that is wasting efforts and adding complications instead of helping. Being more like puzzle for fun that practical application.
Those want to keep close to the soul of the hardware and computing should use C or other low level languages, existing for long. Not made recently to make programming easier ... on paper only apparently...
Numbers are not as easy as they seem. Computer numbers are not the same as the numbers from mathematics.
Is "0.5" supposed to be a floating point number or a rational data type? What about conversion of types?
Sometimes you just need to know what happens in the particular language you are programming in.
Which should be improved - failed representation eliminated - wherever and whenever possible. Especially for new languages. And since there are better and worse representations - actually handling - of numbers accross the languages the criticism of doing badly, not making improvements, expecting to know the whims of a modern lannguage in this regard is absolutely founded!
If I had a junior programmer complain to me about this case, I'd simply tell them "you didn't RTFM", because RTFM'ing before you use a language is not only the professional thing to do, but vital to your accurate and correct use of the language.
Sure, language designers shouldn't build-in these kinds of foot-bullets, but then again, kids shouldn't play with guns.
>First impressions apply to languages too.
The duty of responsibility applies to all languages, and it is the users responsibility to understand the language they are attempting to use, before using it. No?
In the extreme case, take something like brainfuck. Everything is in the RTFM, but that doesn't make the language less criminal.
I like to remember that we are not here to program for the sake of programming. We are mainly solving problems. I have a problem that I know I need to solve with "a gun", and maybe I'm a novice to gun usage. After having a look at some guns, I choose the one who seems to be less dangerous and have a friendlier and more intuitive usage. When your very powerful but a deathtrap with 1000 page manual of a gun is used by no one, don't complain with "why you don't use my powerful and clearly better gun!?".
Because the key here is that this is not the 1970's anymore, when all guns were complicated. Learning by doing is the best way to learn, way better than learning by reading (https://thepeakperformancecenter.com/educational-learning/le...). If a language requires me to RTFM is at a big disadvantage with any other language that allows me to easily learn by doing.
I take issue with this - computers are far, far more complex now than they were in the 70's, which is why denying the language-users responsibility for fully understanding the language-designers intentions is such a farce.
>If a language requires me to RTFM is at a big disadvantage with any other language that allows me to easily learn by doing.
There are no languages under the sun which do not require some degree of study, and to claim that it should not be so is simply delusional. All language must be learned before it can be properly used - some people learn by making huge mistakes with the language they use, its true, but the productive, professional use of any and all human language requires its study.
Shouldn't the correct results be expected for the correct statement "1.0 / 2.0" instead?
This is a user flaw, not a language flaw. Nothing in the "1/2" statement implies real numbers .. ?
This is a foolish argument and Guy Steele should know better. What makes 1/2, which has an accurate floating point representation, any more important than 1/10, which doesn't? Every novice programmer, every single one, trips over floating point, and pretending that floating point numbers match our intuition from mathematics doesn't do anyone any favors.
For example, Ruby was designed for experienced software developers. Experienced software developers expect that if you divide an integer by another integer, that it performs integer division, and 1/0 would be 0 in integer maths.
If the creator of Ruby had broadened their horizon, and instead realised that outside the world of experienced software developers, the expression "1/2" means something totally different, maybe they could have chosen a different behaviour.
In a different universe perhaps any numeric literal in Ruby would always be a `Number` (like in Javascript), that for division would instead return a `Rational`. So that the end user could at the end simply call `.to_i` or `.to_f` or `.to_s` according to their needs.
I don't know if that would be a better universe though. I agree with your point that every programmer eventually needs to learn about floating point numbers and their sometimes surprising properties. And in the end Ruby was designed for programming computers, and the goal of any programming language is to expose the abilities of a computer, which include at the core integer maths, however surprising that maths might be to a novice.
IMO, that would be a tragedy.
In every language that does it implicit type coercion is the cause of a litany of bugs.
It's the cause of many problems in php. It's why people are told not to use == in javascript.
Maybe it is more difficult for beginners. But people are only beginners for a couple of weeks. It makes no sense to design the language to make those couple of weeks a little better and the rest of their programming lives a lot worse.
There's ways to design a number class that doesn't suffer from any of these problems (obviously it would have to sacrifice performance to some degree).
"Cuis-Smalltalk computes with rational numbers."
https://cuis-smalltalk.github.io/TheCuisBook/Writing-your-fi...
The question here is whether or not we should assume integer semantics for numerical values.
Math formulas are an awful language. It served its purpose to discover how to do things better, but it became obsolete many decades ago. People who hold on to these ideas create truly awful languages like Haskell. These languages make comprehension prohibitively difficult. So much so that a lot of people who would otherwise benefit from using the language and would have enough mental capacity to appreciate its higher-level concepts are prevented from doing so by a very superficial thing s.a. syntax.
Also, have had you done any math beyond high-school, you'd not have a problem understanding why the result you see in Ruby makes sense. I think some high schools today also include set theory in math curriculum, which would include concepts like closure, domain / co-domain etc. So... this is really not a good example of the concept you mention.
In general, I think that being correct, minimal and internally consistent is a lot more valuable for the language than to be similar to the prior knowledge newcomers might have. It's nice to be also newcomer-friendly, but that shouldn't come at the expense of sacrificing any of the aforementioned desirable properties.
>>> 1//2
You can try it online at tio.run, they still have Python 2 among their available languages, as well as Python 3.
b = 1
doesn't compare b to 1 and return a boolean.
Lots of novice programmer think that's what equals should do based on familiarity with mathematics.
Using = for assignment doesn't help them solve problems either but it's what those with experience of programming generally expect.
that's sort of ridiculous. Would he be more ok with the output 0.48? what about 0.51? Ironically, in the age of LLMs and non-deterministic output, maybe he would.
Python 2.x
>>> 1/2
0
Python 3.x
>>> 1/2
0.5
For use cases outside AI, and perhaps some niche stuff as a bash replacement since its pre-installed on most distros, it's in a sorta no man's land where I believe there are better options and the industry activity has moved on.
I'm not saying RoR doesn't have its faults, I've left it behind too. But I can't deny its productivity, and even in 2024 I would be hard pressed to name something better for a SAAS company starting out.
I dislike many things about Ruby, but RoR is extremely hard to beat. Basically, everything you need in a stack is provided or readily available in the community.
Of course if we're being really real, the first piece of advice should actually be "Can this be Wordpress?"
Yup. I’m a huge fan of rails, but I’m an even larger fan of not writing code in the first place.
If a spreadsheet can solve your problem, I’m going to suggest a spreadsheet.
If a vendor can solve your problem well enough, I’ll suggest a vendor.
My time, energy, and attention is valuable. I’m here for the problems you can’t or shouldn’t outsource.
With wordpress:
- you are now suddenly also a database administrator
- the different people touching the admin interface will break things in subtle ways
- welcome an incomprehensible security nightmare into your life
Yes, wordpress is extremely easy to set up. Maintain? Nope!
Good managed wordpress starts at like $10-20/month. All those problems you mention go away, you can even do things like rollbacks if someone breaks something. If what you want to do fits into what WP can do pretty well, it's a no-brainer.
I co-organise an event with a non-technical friend. Previously it was using some sort of a custom cms which was hell. I set it up with Jekyll, created a GitHub account for my friend and taught him markdown (five minutes perhaps). Yes he breaks things occasionally. That's fine! Everything is versioned, so I can easily fix things.
...well, congratulations!
When the successful company was founded back in the noughties RoR was arguably miles ahead of everything else. An argument could certainly be made about time-to-market and iteration speed. For the later there were a lot of other viable options available..
> even in 2024 I would be hard pressed to name something better for a SAAS company starting
My sense, at least looking at the stacks for tons of recent YC companies, is that most startups are all in on JavaScript these days. Might not actually be better(though I prefer it), but that seems to be the trend.
I can think of lots of reasons to prefer Python over Ruby, but asyncio in Python compared to Ruby’s async is not at all one of them.
I like Python -- I use it more than Ruby -- and asyncio is fine, just, IMO, not an advantage of Python over Ruby.
Trying to use it for application code, eventually ends up in pain, rewriting code into C and calling it "Python".
Eventually if the JIT POC from 3.13 evolves to something that can compete with a Common Lisp, or Smalltalk JIT, then I can change of opinion.
Note that I only mention those two languages on purpose.
https://rubyonrails.org/doctrine
They are philosophical vastly different languages. This isn't to say one is better or worse over the other. Just use the right tool for the job. If I was going to do anything with machine learning or data analytics or LLM's, Python is hand's down the right tool for the job.
However, If I were to setup a full fledged web application that required a really short time to market with some complex features, I would probably choose Ruby because of Rails.
For what it’s worth, language _really_ built for humans probably looks more like Excel or Max/MSP (or both) than like any traditional language - at least those use our embedded spatial reasoning rather than laundering everything through a text serialization.
I'm sorry, that's just ridiculous. Perfectly in line with that load of blather from DHH though.
> Just use the right tool for the job.
Oh, cut the crap. Both statements have nothing to do with reality. If we go by your definition, there’s no reason for Ruby, because Python will always be righter tools for the job.
For a lot of developers though, basking in the glory of ambiguous poetry, documented in some random funny blog, is not what puts more and broader smiles to their face while urgently trying to decipher their predecessors sensibilities, enshrined in mind bending meta-hairballs.
It is not just the job, some people just really don't like Ruby.
Why do you think they do that?
Answer: To make algorithms easier to understand for other humans.
Python more closely resemble pseudo-code compared to Ruby.
Hence Python is more aligned toward "built for humans" than Ruby.