Hacker News new | past | comments | ask | show | jobs | submit login
To Ruby from Python (ruby-lang.org)
210 points by mariuz on July 16, 2022 | hide | past | favorite | 294 comments



I loved coding in Ruby. I also wrote a Ruby book for Apress, my last book for a major publisher before starting to self publish on leanpub.com/u/markwatson

Two factors made me leave Ruby behind: I am somewhat known for Common Lisp, and so CL jobs would come my way. Also, in the last 8 years, a huge amount of my work has been in deep learning and Python wins there.

Anyway, Ruby was designed to make programmers happy, and it is a big success at that.


> Anyway, Ruby was designed to make programmers happy, and it is a big success at that.

I switched to golang from ruby and I definitely don't miss the ctrl+f hunts to figure out the source code of functions in ruby programs. its even worse when dynamic programming is used to create 'helper' methods (e.g. `{attribute_name}_changed?`) that you have to read through lines and lines of code to find anything.


If anyone very finds themselves hunting around for a method definition like this,

    method(:blah_changed?).source_location
is your new best friend.


True, but then you have to run the program and figure out how to call whatever the calling function is.


Ruby, like Lisp, really really heavily prefers and benefits from REPL-driven exploration and development.

You don’t necessarily have to figure out how to call the calling function, but you will want to use the REPL to evaluate the above code in an instance’s binding with instance_eval or the like, or use Class.method(:blah) to get the method object, or etc. Or if this is Rails, stick a binding.pry in the controller and get a REPL at the point while you’re interacting with the app in a browser, etc.

The ability to get a full REPL anywhere and explore and manipulate the program interactively is one thing I deeply miss when working in less dynamic languages.


Also true, but worth noting that this is generally a trivial thing to do for Ruby codebases

I switched from Ruby to java/kotlin and am consistently frustrated how hard to impossible it is to start a jvm app locally. But also realize that doing so is fairly useless since there’s no interactive repl anyway.

It’s just a different way of programming I suppose.


Yes, its easy to run, but I found that re-creating 'production' input parameters annoying. In rails, that might be setting the request state (set current user, gen a jwt tokens, etc.) which is required for debugging views or controller code.

I also found ruby made me worse at leetcode problems, since my default debugging taught me to run the code, instead of think through program state.


> re-creating 'production' input parameters annoying

Just curious, annoying relative to what language/pattern that does it better?


With Ruby, there is more state stored in the classes than more functional languages (e.g. go-lang or python). Setting that state with ruby in rspec tests is annoying since its not always clear what types and mocks you need without running the code.

Since golang is typed and uses dependency injection, its very easy to know what inputs you need or mocking out values.


Huh, that prompts the thought that it could be cool to have some sort of tracing tool that captures all the state changes associated with a particular HTTP request. I wonder how easy that would be to put together?


I find most things by jumping into pry at a break point and inspecting it. Works really well, but isn’t something that’s common in other languages so people don’t think to do it.


Debugging with pry is annoying for complex inputs (api param with lots of Json or lots of state). You end up making a complex scratch page notes trying to collect all of the inputs to just test the function call.

When the code causing issues is within a gem (and outside of your VSCode project directory) it’s also annoying to find the gem path and source code. Solvable, but time consuming.


> Debugging with pry is annoying for complex inputs

Debugging for complex inputs is always hard, but I’ve yet to see a language do it better than Ruby/pry. Write a test that sets up your complex input, slap a pry in it, use the repl to debug/explore/iterate, then leave the test in place when you’re done.


Go forces you to use dependency injection, which makes it much easier to have simple structs (objects).

With Ruby (and specifically Rails), so much state is tacked onto ActiveRecord that its difficult to mock/debug apis. For example, calling `#find()` creates a new object or trying to stub out `User::ActiveRecord_Relation` in tests is frustrating.


Ah yea, ActiveRecord has some cruft like that for sure. That’s probably why it’s so common to use an actual database for testing and mocking data. Which has its own set of benefits/drawbacks.


bundle show gem-name bundle open gem-name

Fix the last one


yep, gotta run a bunch of terminal commands to get to the file path. And then have to modify the gem's source code to add debugging lines. When you're done, you gotta re-install the gem in case you didn't revert all of your debug tweaks.


Isn’t that more or less true when looking for a bug in a third part library in any language?


It's not like Golang doesn't come with its own set of frustration. Several things I miss from Ruby/Rails

1) An interactive shell. It's sometimes super convenient to run Rails c and just call whatever worker or model method you want...not really doable in Go.

2) Ruby's Enumerable. Even with Generics, manipulating and finding elements or sorting collections in Go is much harder and more verbose than Ruby. In web apps we do only this most of the time.

3) Rails. I just miss one big batteries included framework. No such thing in Go afaik.

4) Expressiveness. There is no ternary operator in Go for example, as the philosophy of Go is to reduce language keywords as much as possible. As a result you generally have more code and are less expressive. While there are pros to this I personally see this as more of a con.


1) true. The unit tests tend to fill that gap for me thought. 2) +1 3) There are a lot of Go frameworks that replicate rails framework. At my job, we use Go-kit. 4) yep. Go is too simple.


I looked around at Go-kit just now, not really getting it, it looks like a list of resources and there's a bunch of frameworks listed. Doesn't look opinionated or am I wrong?


This is my least favorite part of Ruby (coming from a Ruby fanboy) and I actively avoid patterns that make methods hard to find when writing it. Though I think I’m probably in the minority here.


I use ctags that are rebuilt in the background on each commit or pull, makes find source code of definitions trivial.


Does ctags work for inspecting source code of gems?

Does it work with dynamic method definitions? ‘define_method(“#{req.id}_log”)’


Genuinely curious, would Ruby be your desert-island-with-only-one-language language?

If not, what would your language be?

Of course, you could - at least in theory - take any language and implement some other in it...so I guess maybe there’s a question of is it easier to write Lisp in Ruby or Python in Lisp, etc.?


That would be Common Lisp.


probably you would want C on a desert island. you could write your own ruby or python or anything in it.


You could also write C in Ruby, so I'm not sure how that's relevant.


I tried for many years to be happy with Ruby.

Ended up leaving it behind because more often than not, when I revisited a Ruby codebase after a while, Bundler would complain and tell me that I’m supposed to run `bundle install` something, and sure enough, doing just that sent me into a rabbit hole of errors.


If you switched to Python, then you probably traded your `bundle install` errors for `pip install` errors ;-)


Pip doesn't do dependency resolution either... I spent a day trying to pin down issues migrating a project from pipenv to poetry because poetry did not agree with pipenvs resolution.


AFAIK, pip does dependency resolution, you can disable it via --no-deps

https://pip.pypa.io/en/stable/topics/dependency-resolution/


Excuse me I should have said conflict resolution.


For what it's worth that's also been my experience with some Ruby projects I have. Usually bum-simple Jekyll web sites in response to a dependabot warning.

It's almost always a fight to get going again and it seems so needless. Like just reinstall the gems from scratch if you need to, jeez, this isn't even a CI I don't need the gems to be literally identical to what was here a year ago, I just need "bundle exec jekyll serve" to actually do something...

That all said I still like Ruby as a language, it brings the good stuff over from Perl while being as expressive as Python and pretty damn fast to boot. And I recognize Python also has similar dependency challenges. But this is very irritating.


> It's almost always a fight to get going again and it seems so needless

Hm. I do ruby webdev stuff for over a decade now (first ramaze, hanami now) and use a self baked overlay that manages multiple hanami apps and capistrano to deploy them and shares plugins to all the webapps (like a blog component).

I love it. Sure, there were problems with bundler and some with capistrano too but overall its very pleasant for me.


I am sure it will not bring you back :) but in case others might read this and have the same issue the solution is to config bundler to install the gems locally inside a project folder (I usually go for ./vendor/bundle) and the add that to .gitignore

This way if you work with multiple projects each using different Ruby versions and different gem versions you don't keep to keep reinstall the gems whenever you switch the directory.


"sure enough, doing just that sent me into a rabbit hole of errors"

I did that exact thing in a project last weekend, and ended up spending 5 hours trying to fix the horrible mess that ensued. I only tried doing this in the first place because bundler errored out on installing one of the gems, and I figured a "clean" local setup would fix things.

For some reason the project still wanted to use global gems, and when I tried to delete them to force it to use local gems I broke everything. In the end I just installed RVM with a clean version of Ruby and let it use global gems.

I'm sure I did it wrong, but the errors I got were just insane. I couldn't delete gem X because I had gem Y installed, I couldn't delete gem Y because I DIDN'T have gem Z installed. I couldn't install gem Z to delete Y to delete X because my installed version of gem Q didn't match the version bundler wanted, but I couldn't install a new version of Q because of reasons...

After reinstalling the exact same gems (through RVM instead of the apt-get version of Ruby I had before) things still didn't work, and it took me quite a long time to figure out that the error "your version of [some YAML gem] is wrong" means that you have to add a line of Apache config telling Passenger to use Ruby.


I am sorry for your experience with this.

I am just trying to understand your case. So if you have a Gemfile in your folder and then execute something like

     bundle config set --local path "vendor/bundle"
Then all gems should be installed in ./vendor/bundle.

I am not sure how Passenger works (as I am mostly using Puma). I am assuming based on what you wrote that the issue was with the gem passenger itself which is used by the Apache config and it is not a library used by your project.

I don't think the issue you encounter is representative to Ruby and it's ecosystem. I think any module loaded to Apache will probably need a way to know how to load some other modules and probably passenger (via Apache config) needs to know where to find the dependencies it has. Passenger is a web-server so it has its own dependencies.

My suggestion was about the dependencies of your own project.

I am usually doing projects with Rails and Sinatra + Puma + Nginx or Apache and in both of them with the setup I shared I did not encountered any problems releasing the project in production with bare metal configuration (via Capistrano for example) or using Docker.

I am not saying that my experience is representative for everything in Ruby ecosystem. I started using Ruby in 2007 with Ruby 1.8 and I do agree that we had some rough years in the ecosystem specially when working on multiple projects simultaneously.

But now, my experience is a breeze.

On a tangent, I have more problems with npm/yarn than with gems. For example I just started to upgrade an old Rails app to Rails 7 and suddenly on my Mac OS a npm package needs some kind of Python version with some libraries that in turn seems to require some other stuff. Even if I am more like a dinosaurs and prefer to run locally all projects (thank maintainers for asdf) for this one I let it go and put it in docker :)


Thanks, I'll give it another try some day and try switching to local gems. I used the exact command you suggest and it didn't work, but I don't remember why. Unfortunately I didn't keep all the logs, so I don't remember exactly what all the errors were, but I think Passenger was the cause of about half of my woes and I'll look into replacing it.

Bundler was pretty damned useless though, I've had a much worse experience with that than I've ever had with yarn, pip or npm. It really shouldn't be necessary to nuke the whole Ruby installation to do a clean install from a Gemfile, and the errors it produces are extremely misleading and unhelpful.

I'm not really a Ruby dev, I just inherited an ancient Rails project and had to make it work (upgrading from Rails 4 to 7 in the process), so the project setup is less than ideal. I'm sure a lot of my problems were self-inflicted ;-)


Thank you for replying to give more context.

You have some courage to go to a Ruby project and try to upgrade Rails from 4 to 7. I hope it will go as smoothly as possible. Props to you!

I am not sure if it helps and maybe you know these, but just in case here are 3 URLs I keep at hand when I upgrade Rails:

1. https://railsdiff.org (where it shows diff from sources)

2. https://www.fastruby.io/blog/tags/upgrades (they have some great articles in cases of some upgrades)

3. https://guides.rubyonrails.org/v6.1/upgrading_ruby_on_rails.... (there is page for each version just replace v6.1 with the major version that you need)

And also if you have not already started may I suggest you don't go directly from Rails 4 to Rails 7 but just try to go to each major(ish) version:

   Rails 4.0 -> 4.1.x
   Rails 4.1 -> 5.0.x
   Rails 5.0 -> 5.1.x
   Rails 5.1 -> 5.2.x
   Rails 5.2 -> 6.0.x
   Rails 6.0 -> 6.1.6.1
   Rails 6.1 -> 7.0.x
For each version there is an upgrade page. Eg:

- https://guides.rubyonrails.org/v4.1/upgrading_ruby_on_rails....

- https://guides.rubyonrails.org/v5.0/upgrading_ruby_on_rails....

- https://guides.rubyonrails.org/v5.1/upgrading_ruby_on_rails....

You might want to give a try to this gem: https://github.com/fastruby/next_rails. I did not used it so far but I would have a Rails 4 app I will probably try to use it.


Thank you! Those resources would have been incredibly helpful 6 months ago, haha.

It was less courage and more desperation, as the project was running on a server which died before Christmas and we needed it back up before January. The last major change to the project was ~6 years ago and nobody had touched it in ~5 years, so there was nobody around who had the slightest idea how it worked. Props to Rails for just working for that long with practically zero maintenance. In case you're wondering why we had critical software running on a single server with no maintenance: student organization.

We weren't able to find/install an Ruby installation old enough to run the project, so we decided to just upgrade. It was the brute force approach of "run the project, see what error pops up, google the error, fix the error, repeat". In hindsight, this wasn't a good idea and I could probably have found the right Ruby version with a bit more digging, but it works at least.

It's just a handful of pages with minimal code though, so now that things are less desperate I'm considering just starting from scratch with a clean Rails install and manually recreating them. Just need to deal with the legacy database schema and make some changes to it.


> I did that exact thing in a project last weekend, and ended up spending 5 hours trying to fix the horrible mess that ensued.

What gem(s) were giving you issues? I can try and replicate it if you’d like.


Thanks, but I don't remember all the details and couldn't replicate it myself if I tried.

The initial "bundle install" failed on "therubyracer" (0.12.3) on Ruby 2.7.4 (installed with apt). As I had previously installed it just fine on the same machine and it now works just fine on Ruby 2.7.4 (installed with RVM), that doesn't make much sense.


Gems are versioned already, and if you're working with different Ruby versions, your gems are already isolated to that version.

No need to vendor gems.. Vendoring millions and millions of duplicate files is for the node crowd.


> Vendoring millions and millions of duplicate files is for the node crowd.

and PHP


Gems are versioned already, and if you're working with different Ruby versions, your gems are already isolated to that version.

No need to vendor gems..


as an other perspective this happens because you are not pinning the versions correctly. your gemfile should have an explicit version of every major gem you use and the gemfile.lock should have the resolved dependencies already made out and committed. you should also have a particular version of ruby in mind and locked with a .ruby-version file in the project root. This should work flawless in all but the oddest edge cases.


Leave a javascript project for more than 2 weeks and you'll get the same thing.


as someone who has used ruby for years, and more recently got a job working in python, I think the most significant difference between the two languages is how they handle imports.

in python every file is also a module, and all imported symbols are namespaced within that module (unless you deliberately use "import *" to bring them into the current module's namespace). so you can define function foo in module bar and use it via "import bar; foo.bar()".

in ruby, modules/namespaces and files are two entirely orthogonal concepts; you define a module by enclosing code in a "module Foo; ...; end" block, and any file can open up the same module and add stuff to it. when you import a file it adds everything within it to the current global namespace, which makes it really hard to read a piece of ruby code and see where everything within it was defined.

for the most part where ruby and python differ I prefer the ruby way, but I feel like automatically namespaced imports is something python got right, and was a really big miss for ruby.


The tradeoff is that you don't get to have open classes with a module oriented approach.

Shameless plug: toyed with the idea of imports and "packages" in Ruby some time ago. Recent facilities in 3.1 (or 3.2?) could make the implementation even easier.

https://github.com/lloeki/pak

https://github.com/lloeki/ruby-package

IIRC there's an issue somewhere at bugs.ruby-lang.org about that

EDIT: OMG 8 years ago! /me screams


> The tradeoff is that you don't get to have open classes with a module oriented approach.

A module oriented approach could still have open classes, and if you did an imported module could still mess with classes in other imported modules (it would just also have to import them.)

In fact, you can set variables in other modules and members (including methods) of other classes this way in Python.


i like that! it definitely makes things cleaner in terms of being explicit about where everything comes from.


It's a possible smell in Ruby all right. Ideally, every file has its code in classes or modules that match the file name, so require 'foo' brings in the class Foo and nothing else. But it's possible to do some really ugly hacky stuff, so it more becomes please don't actually do that in a large production codebase.

Python's default of namespacing imports is nicer all right, though I've seen a decent amount of Python code in the wild that has a dozen import * at the top of every file so that grep is the only way to find out where a function is actually defined, just as bad as Ruby.


> so you can define function foo in module bar and use it via "import bar; foo.bar()".

Wouldn't this be "import bar; bar.foo()"?


In Ruby it’s “require” rather than “import.” But it would be more like “require bar; foo()”.

However, Ruby does have auto-loading conventions. So you could define “module Baz{ module Bar { def foo end; } }” in “baz/bar.rb” from the root of your project. You should then be able to call “Baz::Bar::foo()” from any other file in your project structure without a “require” at all. This is not standard Ruby IIRC, but Rails and other frameworks use it.


> You should then be able to call “Baz::Bar::foo()” from any other file in your project structure without a “require” at all.

IMO, this is a downside rather than an upside. It's optimizing for writing rather than reading. This hurts understandability for someone who is not familiar with the code base.


IMO, Ruby optimizes for reading. You simply cannot write concise code in Python as in Ruby. One reason (of many) is because of all the import statements required in Python. In Ruby, there is often only a single require statement, or maybe none at all, at the top of a file.

The difference is convention over configuration. In Ruby, if you know the convention, then you know exactly where everything is defined without needing to read it. In Python, you must read all those import statements to know where something is coming from, not to mention write them in the first place.

And if you really need to know where something is defined, you simply ask it.

    puts obj.method(:foo).source_location
So IMO, Ruby optimizes for both reading and writing.


There is a gem that does that used by Rails and multiple other gems:

https://github.com/fxn/zeitwerk

It is pretty easy to set it up in any Ruby project.


it would actually be:

  require 'bar'

  foo
because require is a regular method and the argument is a string (I mean, you could pass bar without quotes if the string was in a variable called bar, but that's not the scenario being discussed.)

> However, Ruby does have auto-loading conventions. So you could define “module Baz{ module Bar { def foo end; } }” in “baz/bar.rb” from the root of your project. You should then be able to call “Baz::Bar::foo()” from any other file in your project structure without a “require” at all. This is not standard Ruby IIRC

autoload is a Kernel method in Ruby core, so it is standard Ruby, but you have to explicitly register a file to be automatically loaded when a particular method is referenced.

Some systems additionally include code that does this for source files based on path.


Yes, it looks like OP had a typo.


right, that was my mistake


Ruby modules and requires effectively just concatenate a bunch of files that have globals in them and have nothing to do with real modules.


This is simply false. Ruby modules are first class. They're objects that you can pass around like any other object. You can put them in a local variable. You can define methods on them. You can do metaprogramming using them. You can build classes by mixing modules together. You can create modules (and classes) at runtime. Many things that need to be built-in in other languages are just a library in Ruby.

Files and modules in Ruby are two distinct things. While Python makes files one-to-one with modules, Ruby allows you to define as many modules as you like in a file, zero or more.

It's true that if you define a constant at the top-level of a file, it uses the global namespace.

By convention, if you have a file named `foo.rb`, you'd write:

    module Foo
      # Code here
    end
and it's equivalent to what's done in Python. But it also has all the other features.


Your claiming anything wrapped in a module block is a module. My point is that they aren't "real" modules. I know it's debatable what a module is but Ruby's don't meet my definition. Here's why:

Let's say a file had a module in it. It can behave differently based on context.

For example, let's say I have a module A that requires modules B and C. Module C could use module B without explicitly pulling it in because modules are just globals. However, if a different module just requires module C but not module B and no other files required B either, then module C would break when it tries to use model B. If modules weren't global B, would have to require it to use it directly regardless but since it doesn't it's easy to get unexpected results.

In ruby a file doesn't have to declare the modules it uses which is why I don't consider them modules but just global namespaces. So if another module that got required earlier happens to require B, then B can be used because it's just a global.

I'm summary, modules in Ruby aren't modules but just global namespaces.


When I was new to Ruby and hadn't learned the available tools yet, I had similar problems because I brought practices from other languages that didn't work.

Typically, what you're describing is solved, in different ways depending on your setup. I literally never think about the issue you're describing when using Ruby.

- With Rails/ActiveSupport in development mode: Module B is autoloaded lazily when C refers to it.

- With Rails/ActiveSupport in production mode: All modules are eagerly loaded at startup.

- Plain Ruby: There's a few ways, but most gems simply have a file at the top level that eagerly requires all files when the gem is required. Same with an app.

If startup time becomes an issue, you can configure the autoloading at the top level, and the appropriate file will be required lazily when it's referred to. But at that point, just use ActiveSupport.


That’s kind of what the GP meant…

A require in ruby would just import whatever those files defined in the global space. They may be “modules” (which probably have different meaning in ruby vs Python), but they’re globals nonetheless and inventive coders put all sorts of weird things into the global space instead of following convention of one file one module.

It’s a fundamental philosophical difference between the two languages - is it a good thing to let programmers do the wrong thing? Python makes it hard to do so, Ruby celebrates the flexibility.


Agree Ruby code is super hard to understand, this seems largely overlooked by the community


I have never had this issue, other than with some of its more complicated constructs (like class << self).

And amongst the community, this is not a complaint I hear very often.

I still think of Ruby as the language that maps closest to my thought process when programming.

What is troubling for you?


Agree the GP can be more specific.. that said in response to

> amongst the community, this is not a complaint I hear very often.

Those unhappy with the state of ruby probably moved to different languages instead of hanging around and complaining…


Specifically talking about the "magic" import system


(out of curiosity) What is hard to understand about Ruby?


One thing that drives me up a wall about Ruby (versus Python) is that function and method calls have basically zero context so you have _no idea_ where a piece of code actually lives without investigating or relying heavily on IDE features.

Call a method on the current object? You just punch in the method name. There's no leading `self.` to indicate you're interacting with this class.

If you require a library, the required code can be literally anything. Namespacing of the classes and functions is optional. Hopefully the library author did something reasonable. And even if they did, that namespacing isn't gonna match your require statement.

Python's explicitness around this stuff is absolutely a breath of fresh air compared to Ruby's wild west approach.

I also gotta say I'm not a fan of the fact that there tend to be one or two method aliases for lots of stuff in the standard library. It makes code easier to write but harder to read. As an example: the Array class has push, append, <<, unshift, prepend, and insert methods. Append is sensible. Do we _really_ also need it to be called push? and do we _really_ also need <<?

Prepending is an uncommon enough operation that "array.insert(0, object)" should have been sufficient.

I understand the intention behind Ruby's design decisions but I personally feel they do programmers a disservice. Writing (or even reviewing) Ruby without a featureful IDE is an absolute chore for projects of any appreciable size.


First, I do agree that Ruby ecosystem could do some things better and IMO in the last year or so there are some good projects happening in this space.

Second, I cannot refute your arguments. That is because I see them more of a personal preference or somehow expectations based on your programming history (I might be wrong of course as I don't know your programming history). As Ruby feels similar with other programming languages I think some kind of expectations of how things should be kick in. But Ruby has a specific design that is for example focused on "brevity" or "beauty" among multiple other thigs.

I worked so far in the last 15 years in multiple codebases in Ruby: large Rails apps, Sinatra apps but also smaller webapps. In all kind of combinations some of them I built and maintained alone and some of them were built in a team.

This being said, I don't remember a time when we have a big name conflict because we used a couple of gems and they were colliding.

I do remember when some colleagues were over using meta-programming too much or changing default behaviour of stdlib. But this can be fixed with coding guidelines and I take imposing coding guidelines over removing these features from Ruby as sometimes they make more sense than anything else.

Regarding ".self" I don't feel the need to use it. Once I got the call hierarchy I prefer not needing to write "self" where it is not needed.

But also in Ruby everything* is an object thus I feel that self is a bit more powerful so it should be used when when it is really needed like when defining methods and you want to indicate where that method belongs.

Regarding having multiple ways of doing the same stuff in stdlib, here we diverge as I prefer this diversity. I really like that we are multitudes in this community so code can be written in multiple ways, it is for me almost like a form of art when I read some case and I see how expressive can it be and how easy it is to get what it does even if it uses a different std method than I normally use.

I think if you want to get Ruby you should start by thinking that Ruby is a programming language created (IMHO) with the intention to let you express yourself but also make it so that other people should easily understand it.

It also worth reading this beautiful essay by the creator of Ruby: https://www.oreilly.com/library/view/beautiful-code/97805965... (sorry I could not find a direct link unwalled) and from there you will get a glimpse of why Ruby is in a specific way and see if you agree or not with that.

For example regarding your point with no leading self, Matz says in that essay "programs should ideally contain no unnecessary information" thus a lot of what you see in Ruby including having prepend instead of "insert(0, object)" is a manifestation of that.


Of the languages I've used in anger, Ruby is perhaps my favorite language to write code in. I think Python and Java are both easier to use in a large shared codebase, and one of the biggest reasons is that it's sometimes a lot harder to see where a method is defined in Ruby. Being able to use method_missing to automatically build a wrapper class or shim is super easy and pretty cool, but it can make it much harder to piece things together when debugging.

I worked at a company that had a codebase split fairly evenly between Ruby, Java, and Python. The Ruby stuff was the hardest to understand, and I think it was primarily because of using patterns around dynamically method definitions.


was specifically commenting on the "magic" import system


It can be, more than other languages, but doesn’t “have” to be. Complex code is a smell.


Meta programming stuff can be very confusing for non native rubyists. Generally thats something one should avoid in shared/production codebases.


As someone with a bit more of a decade of Ruby experience: meta programming can be very confusing for native rubyists, too. There is a place for it (my personal take would be that this place is usually libraries/gems, e.g. if they need to be flexible to adapt to the consumer’s project), but it’s usually not code that needs to „convey a story“ (usually business logic) or needs to be debugged/observed.


For some reason it reminded me of _why's unholy:

https://github.com/devyn/unholy/blob/master/README

> You know, it's crazy that Python and Ruby fans find themselves battling so much. While syntax is different, this exercise proves how close they are to each other! And, yes, I like Ruby's syntax and can think much better in it, but it would be nice to share libs with Python folk and not have to wait forever for a mythical VM that runs all possible languages.


That's funny.

In a previous job I was the biggest Ruby supporter on a team of Pythonistas. To show them how similar the language was I translated a couple of their scripts from Python to Ruby... it was surprisingly easy and the linecount was about the same


That is all good and well, but which was easier to read and debug?


That really depends on your background... I find Python harder to read than Ruby


> On a team of Pythonistas


This is very good timing for me, I just left my python dev job and am starting a job where they use Ruby. I’m very excited to be using Ruby again, it’s a much nicer language to use for a variety of reasons I can’t possibly enumerate. Ruby’s stdlib and surrounding documentation is pristine, I vividly remember being appalled when trying Python for the first time and thinking where the rest of it was. The major issue I find with Ruby (and that I saw mentioned elsewhere in this thread) are imports / requires, which is much too magical in Ruby; Python’s import is way saner


Ha, I just did the same thing. It's definitely a bigger shift than I expected!

I think the biggest difference so far is the quality of dev tooling. Python's native type system has enabled a lot of really good editor functionality. Ruby has Sorbet, which seems good, but it's _much_ slower and more cumbersome. After a save to fix a red squiggly line, there's a 5 second delay before the typechecker / linter figures out the code has updated, run, and updated errors in-editor (note: this could be due to the size of the repo, but it's still a pretty tough experience)

In the company codebase it's pretty well set up and VSCode come pre-configured. But yesterday I tried setting up a little ruby project on my personal machine and couldn't figure out the combination of extensions to make basic auto-complete and jump-to-definition functionality work at all. To their credit, Microsoft has made Python development incredibly straightforward by giving a One Best Way To Do It and making sure there's good LSP support.

Python has its warts for sure, but I've grown to really appreciate the language and its ecosystem.


Ruby's imports aren't magical by default. Autoloading frameworks make them so.


The fact that ruby makes this possible means the community will take advantage of it, to the point of abusing it sometimes. This hurts understandability of the code base. Too many times I would open a ruby code base and spend significant amount of time tracing where things are defined (mostly relying on suboptimal searching) because of the lack of explicit imports/requires.


An easy way to do that is to use the Pry gem. If you're in a pry console, you can use `show-method Thing.foo` and it will show you the method, which file and line it's defined in, etc. Pry is a great tool and this feature makes is a blessing.


Python can do this magic too and the community hasn’t abused it. Every part of modules and module loading is swappble, mutable to the point where modules can be arbitrary classes if you want.

It would be 2/10 difficulty to add transparent module autoloading to Python because you can just hook into module loading and arbitrarily modify or replace the module objects.

You wouldn’t even need a courtesy import because the main module is still a module.


Even without an autoloader, Ruby has a single global namespace, so you have access to everything that has been required at any point from anywhere.

If anything autoloaders enforce that constant names match filenames, making it easier to locate the source.


This page is old [1], is there any reason or context regarding why it is interesting now?

[1]: Archived in 2006: https://web.archive.org/web/20060925032432/https://www.ruby-...


The page has been slightly changed since then, but it is still quite outdated. Python 3.6 (2016) has formatted strings, and new/old-style classes thing isn’t relevant at all since Python 3, but wasn’t that much of a deal with Python 2 at the end of its lifetime (~everything was new-style by then).


Ruby is a neat language that I don’t know very well. Blocks are nice. The pattern matching in Python 3.10 compels me to stay.


ruby has some nice pattern matching too https://rubyreferences.github.io/rubyref/language/pattern-ma...

not sure if python has better matching though but the ruby stuff is pretty compelling.


Ruby is a beautiful language, but it lost the race against Python long ago. - When I first looked into both some ten years ago, I was undecided what to choose. But even back then googling problems led to much more professionally written blogs / articles about Python. Articles about Ruby were not as profound. That’s when I decided to go with Python. And it was the right choice.

## Edit

What I miss in Python: Ruby always returns a value and does not change structures in place. For instance `data.sort()` in Python will return `None` and change the order in `data`. Ruby won‘t do that and will return a sorted copy. Like Scheme (a Lisp dialect). In Scheme methods changing data have an exclamation mark as suffix: `(sort! data)` vs `(sort data)`, and it will always return a value. Unlike Python.


> Ruby always returns a value and does not change structures in place

The not changing structures in place bit is only for std library things… nothing is stopping code you write from modifying structures in place.

Ruby has the ‘! means it mutates’ convention as well, and the built in methods use it (for example, .sort! on an array modifies the array in place just like your Scheme example). However, again it is just a convention… nothing stops a ruby developer from modifying something in place without a !, or making a method that ends in ! that doesn’t modify in place.

The common theme in Ruby is ‘nothing stops you from…’


What “lost”? They’re both Top 10 most used languages with tens of thousands of open positions currently available for someone competent in either.

Python’s the more frequently used of the two industry-wide, but it seems like they both won, just with different crowds.


I think it's that Ruby has mostly been relegated to Rails, while Python sees use in backend systems, data science, scripts, and as a popular first language.


As a professional data scientist, I kinda wish ruby had won for this area, as it's much closer to R conceptually.

Python is fine, the metaobject model is pretty nice, and I've stopped noticing the double underscores ;)


You may be making a point about what happens by default (and perhaps Ruby’s similarity to Scheme?), but in Ruby you can also say `data.sort!` if you want the `data` array modified in-place. It’s still returned, though.


The hardest thing for me to understand in Python was the baffling tendency of methods to return None.


I think I have read somewhere that they return None in order to explicitly signal that the object has been modified in-place. For example, if you want to have one method which sorts in-place, and another which returns a sorted copy, if then both methods returned a sorted object, it would be too easy to mistakenly call one instead of the other, creating a bug which might not be found for years.


If you're learning Ruby, there is a free copy of my book available (CC BY 4.0 license):

https://leanpub.com/rubyisforfun/ - English

https://leanpub.com/rubyisforfun_ru/ - По-русски

(if you found typos, you can update the manuscipt https://github.com/ro31337/rubyisforfun)


For me there wasn't ever question of "How?" only "Why?".

One time I landed on Ruby project and I didn't have any trouble fixing bugs and implementing minor functionalities without really learning Ruby only knowing some Python.


I feel similarly about python. I use ruby all the time, but can do things in Python just fine, especially if I have an existing code base that I am working on.


Tensorflow? Numpy?

No idea what else might be the reasons to learn Python when you know Ruby.


Because there is a lot of Python code at my work.


> You can read docs on the command line (with the ri command instead of pydoc).

I've been using Python for a long time, but somehow I never learned about pydoc. It looks like a pretty handy tool!


Arguments about the “superiority” of Python vs Ruby always sound a bit like arguments about the “superiority” of Italian vs Spanish. If you squint a bit they’re basically the same.

(By that simile, Lisp would be Sanskrit?)


True, but 85 million Italian speakers vs 500 million Spanish speakers so that tells you something. Sure the Roman Empire was big back in the day but the time has passed.


And yet I can only read Dante’s original work in Italian!


> Unlike Python, in Ruby… There are a number of shortcuts that, although give you more to remember, you quickly learn. They tend to make Ruby fun and very productive.

I use Python, but have never tried Ruby. The ecosystem for Python packaging is so chaotic that I’ve considered switching.

Any Pythonistas/Rubyists here that could point out some Ruby shortcuts that make you more productive?


There are thousands of little things that make Ruby nice. They all add up to a more enjoyable experience. I'll give you one tiny example:

Python:

    from datetime import date
    if date.today().weekday() == 5:
      print("It's Saturday")
Ruby:

    require 'date'
    puts "It's Saturday" if Date.today.saturday?


I prefer the Python code here. The import is explicit, so if you want to know where "date" comes from, that's easy. In a modern IDE I don't write imports manually anyway.

You could write it as a one-liner in Python as well, but I would prefer not to.

  print("It's Saturday") if date.today().weekday() == 5 else None
That the Python library doesn't define constants for days is unfortunate, but not a limitation of the language.


Why do I need to remember that Saturday is 5?


    >>> import calendar
    >>> calendar.SATURDAY
    5


Yeah, I'll take `Date#saturday?` thanks.


¿Por qué necesito recordar que el sábado es "Saturday"?


Rest of the language doesn't bother you?


Porque en ese caso no usas un IDE moderno.


Tampoco "if", "for", "while"...


You are allowed to define your own constants.


S looks like 5 if you squint. not hard to remember


So Sunday?


No! Sunday is 6 because "Sunday" has 6 letters.


That's it, I'm switching to Python!


The ruby oneliner looks way cleaner and more natural than the python one.


Would you ever use either in a real codebase though?


Well, yes. In fact it's the recommended style for simple conditions.


As opposed to a fake codebase? This sounds a bit like https://en.wikipedia.org/wiki/No_true_Scotsman.


As opposed to either a snippet in a REPL or a comment on hacker news.


The code is part of a standard, used-almost-everywhere library, `date`. Code in these libraries, from what I undersand, have a ton of thought behind them. It might follow that there were good real-life use-cases ready to use the code that influenced its presence in that library?

Also: https://github.com/search?l=Ruby&q=Date.today.saturday%3F&ty...


So the answer to the original question was simply “yes”.


No, not as opposed to a fake codebase. I think it's clear what they meant.

One incredibly common alternative to a codebase is a script.


Ok, I'll bite. I don't, I think it was a knee-jerk reaction post that didn't think things through for more than a couple seconds. I'm guilty of many of these, it's not the end of the world.

I know many "scripts" that have far more curation, history, and accumulated effort than any "codebase" sensu your post, I don't think this has any relevance to his question. Use is use. "Real" or "Fake" in terms of a standard library like "date" (see my other post) just isn't a useful dichotomy IMO.


I'm not a fan of gatekeeping either, but on HN the proper etiquette is to give the poster the best possible reading of their post.

I just interpreted their post as talking about bigger, organized projects, instead of small scripts.

I personally draw a distinction between "codebases" and most everything else. One of the key differences is "would I start a new git repo?"

"Real projects" get a repo for sure.

It's not too diminish smaller projects. Almost all of the most useful things I've ever built were very small. But when you can grok the whole script, and you're likely to be the only user..."codebase" isn't the word I'd bust out.

I'm sure you can think of many exceptions to this. Thinking of exceptions is a good quality for programmers.

I don't think the word "real" (codebase) was a very important modifier.


Agreed, in complicated Ruby packages with layers and layers of dependencies it quickly becomes a nightmare to identify which class comes from which import, where it’s defined, etc. Never an issue in python


Preferring the Python import syntax doesn't preclude preferring the Ruby library design.


The else None is redundant, though. If you want a one-liner in Python just ignore the indentation:

    if date.today().weekday() == 5: print("It's Saturday")


If I only used Ruby I'd probably prefer the Ruby example. As someone who jumps across many languages, I prefer the first. I found Ruby's approach to things was at many times too clever, to the point of becoming unreadable and cryptic. Python prefers clearness over cleverness.


How is computing a weekday where the caller (and reader) must know that Monday=0 not cryptic?


I agree that's a little weird, but for me the consequence of it is small.

I'm going to catch that bug quickly.

When Ruby does a magic import, that would sometimes cost me a lot of time tracking it down when I first started with Ruby. I don't remember it being a big deal anymore after a couple of years.

(I like both languages)


Especially when other systems like cron have used Monday=1 forever


I think the if being an infix operator is a bit "cryptic". I mean IMO it is easier to read - and in reality it's not a problem, because it's pretty unambiguous what's going on, but some people get annoyed by "oh you can do that in ruby?" Trigger that happens the first few times you see it.

Probably same with fields/methods with a postfix ?


> Probably same with fields/methods with a postfix ?

It's not a “postfix ?”, it's literally just “?” being part of the identifier name. (It's convention for methods that are boolean predicates.)

Also, Ruby doesn't have fields. (It has instance variables, but they don't have dotted access, so they aren't in the same namespace as methods of the same object the way fields are.)


It's a postfix sigil, even if it's part of the identifier name. You're not allowed to put ? in the middle of an identifier. (I use ruby and elixir, so this is second nature to me. It might not be for others).

I'm aware ruby doesn't have fields, I called it fields/methods because it's a method in ruby but people in other languages will read it as a field, which it is, a good percentage of the time.


I think every language I've used has annoyed me in how it handles dates and times.

I've been tempted several times to sit down and repeatedly read Dershowitz and Reingold's book "Calendrical Calculations" [1] cover to cover until I thoroughly understand it, and then write my own date and time library and port it to every language I use.

That would mean that when I learn a new language I'd have to take some time to port my date and time library, but looking at the languages I already know that would have been less than the time it took me to find and learn to deal with the quirks and annoyances in their libraries. I see no reason to believe that this will be different for any future languages I decide to learn.

Yes, I know this would be annoying to whoever has to take over my code when I'm gone, but I tend to stick around on projects until they reach end of life and so there almost never is anyone that comes after me.

[1] https://www.amazon.com/Calendrical-Calculations-Ultimate-Edw...


I cordially invite you to look into the clusterfuck that is Go's treatment of date formatting. Everything else will seem clean and logical by comparison.

Per https://golang.cafe/blog/golang-time-format-example.html:

> Format returns a textual representation of the time value formatted according to layout, which defines the format by showing how the reference time, defined to be Mon Jan 2 15:04:05 -0700 MST 2006 would be displayed if it were the value; it serves as an example of the desired output. The same display rules will then be applied to the time value.

So, for instance, if you want to format a date in ISO format (YYYT-MM-DDTHH:mm:ss), you have to figure out how the reference date would be displayed in that format ("2006-01-02T15:04:05"), and pass that as the format string.


If I didn't know that was actually an accurate description of Go date formatting, I’d think it was satirical.


And durations:

    package main
    import "fmt"
    import "time"
    func main() {
            three_secs, _ := time.ParseDuration("3s")
            five := 5
            fmt.Println(five * three_secs) // error: type mismatch
            fmt.Println(three_secs * three_secs) // 2500000h0m0s
    }


Good Lord.

EDIT: Wait, I don't even understand how that can work, even in a broken way. Since I guessed 25000006060=9,000,000,000, I guessed that a "second duration" is really just a wrapper around `3,000` (ms) or `3,000,000` (ns) - but neither of those square to 9E9. In fact, the square root of 9E9 is a distinctly non-round number. What's going on here?


I agree, of all the date formatting I've encountered, Go's is the worst.


I can't fathom optional parenthesis. In this code it makes it very hard to know if today is an attribute or a method without knowing the context, meaning I can't scan the code, I have to read it carefully.


It's always a method. Ruby doesn't expose instance variables unless you define accessor methods for them.


I'm going to emphasize this as well. Everything is an object, and everything called on an object is a method. Full stop.

Unlike with other languages, there is no ambiguity here.

In python, you don't know when you dir() an object whether your dealing with a property or method. In Ruby, you are guaranteed it's always a method. A perk is that you don't need parens.


Exactly. Once you get some basics of Ruby, there is no ambiguity, and I find it quite elegant.

And I think I’m in the minority here, but I prefer the cleanliness of optional punctuation.

That feature also allows for fluent DSLs (which seem to be falling out of favor for various reasons), but is one of the reasons I still love Ruby.

To look at the other side, I hate having to constantly add semicolons and parens in other languages. Makes my poor fingers hurt even more than they usually do.


> I can't fathom optional parenthesis.

I think this kind of falls in line with the idea of Ruby giving you sharp knives[0].

Like you I used to have a love / hate relationship with this because it feels inconsistent to sometimes use parenthesis and sometimes not. In a world where you just want to run Python's Black code formatter and always use parenthesis, having to think about when to use parenthesis feels really tedious.

But I think nowadays I'm happy that they are optional because when you want them to be optional it creates a really nice aesthetic to your code.

For example with Rails you can do: `2.days.ago` and then there's also things that feel like a mini DSL such as `validates :title, :body` or `belongs_to :users`. These just look nicer without parenthesis.

With that said, I do use parenthesis more often than not but it's nice to avoid using them when it makes something look nicer visually.

[0]: https://rubyonrails.org/doctrine#provide-sharp-knives


Given the context of this thread (comparison of ruby and python), it should be noted that python also has this. You can decorate a method with `@property` and call the method without parentheses.

It's really nice because it means you don't have to pre-wrap all of your properties with accessor methods. They can just be properties, and if something changes, you swap the property out with a decorated method and you don't need to change any code.


> I can't fathom optional parenthesis. In this code it makes it very hard to know if today is an attribute or a method without knowing the context.

Optional parentheses exist because it is impossible for there to be ambiguity: after a dot, if a name starts with a lowercase letter it is a method (and the reference is a call). If it starts with an upper case letter, it is constant (which is not callable as a method.) There are no other possibilities.

“Attributes” in Ruby are literally just getter methods (possibly paired with setter methods.) There is no object-dot-field access to fields (instance variables exist, but outside access to them doesn't use that syntax, but instead goes through a different set of method calls: Object#instance_variable-(get/set)


Makes reading ruby so much more difficult. Also optional return statements. If you need to refactor a function, it’s not always obvious that it’s relying on an implicit return and super easy to break things IMO


(not trying to convince you as I think in this thread no one will be convinced of anything)

Knowing that in Ruby a method will always return its last executed line unless explicitly returned early makes (at least for me) things very easy to get.

A method with two branches will always return the last line of the executed branch, thus you will always know what the response could be.

But this (IMO) is because Ruby OOP where the main concept is that when you are invoking a method you are actually sending a message to that object thus you should get a response. So Ruby makes this way of working with objects universal.

Once you call a method you will get a response.

I find it strange the other way around, working in languages where I need to put return even on the last line of the method. For example after almost one year of full time backend development in Ruby, I needed to write some JS code. I debugged for half an hour why a short function that I wrote was not working properly until I got it: I need to add 'return' to that last line to get the actual result. This was strange for me :)


> I can't fathom optional parenthesis.

How about optional dots in Scala?

    if (Day today saturday) println("It's Saturday")


Those are worse, but yeah, same principle.


I started writing ruby a couple weeks ago, and it threw me off at first. But you get used to it very quickly.


In case you want (and need) to get how to think in Ruby I recommend you to listen to this talk by Sandi Metz (and in general I recommend all her talks even if they are old): https://www.youtube.com/watch?v=XXi_FBrZQiU

It presents OOP in Ruby in such a beautiful way and it takes a while to start thinking like this.


There may be other things about Ruby that I disagree with, but this is probably my primary disagreement.

It does make code more difficult to understand for exactly the reason you mentioned, and furthermore it can sometimes be disallowed based on the rest of the expression (such that parens are required).

In my own Ruby code, I often use parens when they are optional.


How do you know which 'Date' your are using. How do you know it's the one from 'date'?

That's like using * imports in Python which is a huge pain once you done even minor refactoring.


> How do you know which 'Date' your are using. How do you know it's the one from 'date'?

Ruby allows you to check where a method is defined, e.g.:

    puts Date.singleton_method(:today).source_location
Although in this case it returns nil because it's implemented in C. But it would return the location if you overrode it.

There are various tools to see what the code is doing: invoking a debugger and stepping into the code, tracing the code execution via TracePoint, etc.

The Zeitwerk autoloader which comes with Rails enforces that the class name matches the filename.

Naming conventions, specs, a quick grep and avoiding generic names make it almost a non-issue in my experience - only happens every few years or so, and usually quite easy to fix.

Ruby gives you a lot of power, but it doesn't stop you from shooting yourself in the foot.

https://m.signalvnoise.com/provide-sharp-knives/


Yeah in the Rails shops I've worked with, this "developer productivity" turns into a nightmare with large codebases.


More productive is really hard to say... But I personally prefer Ruby to Python. For smallish scripts, I really like Ruby's backticks for easily executing shell commands. And I find the metaprogramming support in Ruby great for some tasks. I'm not sure if that's what you're getting at.

But especially with "modern" Python features like f-strings and the walrus operator there's not tons of differences that really stand out as "productivity".

One thing to just remember: Ruby is not just the Ruby on Rails community. Rails is a dominate space, but it's not the only thing out there. At the same time, when it comes to scientific computing, ML, etc. the Python ecosystem is incredibly robust. There's plenty of awesome Ruby tools for these things too, but it is not the same.

Ruby and Python are the two languages I primarily teach courses in, and used professionally. Ruby is my cup of tea personally, but they're both modern dynamic interpreted languages.


> For smallish scripts, I really like Ruby's backticks for easily executing shell commands.

Executing external commands is pretty convenient in Python these days, now that we have subprocess.run [1], and passing the command as a list rather than through the shell seems a lot safer.

Doesn't backticks open up the classic command injection issues (i.e. the same kind problem as SQL injection with non-parameterized queries) unless you're extremely careful to manually apply proper quoting in every single case?

[1] https://docs.python.org/3/library/subprocess.html#subprocess...


It would, but most of the time when I'm using backticks, it's because I'm writing myself/my team a little helper script that will never get "injected" with anything. Backticks (and the other more featured ways of doing shell commands in ruby) make it really easy to write what amounts to a Bash script, but with a little more features and readability, or pull in some relevant libraries for some more complex task that would be annoying to do in pure Bash.


Your use case might be different from mine, but I find that usually there is some parameter needed for the command, usually a filename or sometimes the output from another command. Even if you're not accepting hostile data over the network, you probably want it to work correctly for any valid filename.

With a bash script, or shell commands as strings in general, I can usually with some work be reasonably sure that I'm handling filenames with spaces correctly, so that it doesn't explode when it encounters something unusual... mostly, probably, in most places ... I hope. But what about filenames with newlines? Probably not as correctly. Etc.

With a parameterized API, this all just works and is correct for all cases, so you don't have to think about arcane quoting rules.


To be fair, there are some of the more complex ruby libraries that do provide those more protected/parameterized options. In general, even when I wrote a script that took input, I found it sufficient to just assume that if another dev tried to use it for something where it broke, they could work out how to fix it, or rename the file/folder on their system to make it work.


> Any Pythonistas/Rubyists here that could point out some Ruby shortcuts that make you more productive?

It depends on what you want to do.

If it's building web apps there's Ruby on Rails, which is written in Ruby. In my opinion there's no alternative to this in Python. Sure there's Django but it's not the same even though both are considered batteries included web frameworks.

One nice thing is you'll find both Python and Ruby SDKs for a ton of third party services (like Stripe, etc.). That goes a long ways for building things quickly and with confidence.

Ruby's package manager is also a lot better IMO. It supports proper lock files out of the box and everyone uses gems with bundler. In Python it's a mix of pip, pipenv, poetry, etc. all of which have their own unique issues. With Ruby I never felt like I had to battle this aspect of the language. You just bundle install what you want and creating gems is the same process.


> Sure there's Django but it's not the same

Could you elaborate on this? I’ve used both Django and Rails and in my mind they’re direct parallels to each other. For example: in Rails you write migrations and then it generates a schema.rb, and in Django you update your model schema and then it generates migrations.

They have very different philosophies, sure (reading the Rails manifesto made that clear), but from a technical standpoint they have pretty good feature parity.


I think you're only considering the backend part of these frameworks.

Rails is really full stack. Django's frontend story is a joke.


Okay, when I’ve used Rails, it’s with React on the front end, so I haven’t explored that part of the framework.


If one wants to get into Ruby, please stay away from Rails as a first step into Ruby-land.

Look into Rack and Sinatra first, then Rails. Even if it's a short trip it's going to be worth it.


This is personal opinion but these days I'd probably swap Sinatra for Roda[1] for small API services.

It's generally faster, uses less memory and is a really good example of well written ruby code, IMHO. I also really like Jeremy Evans' book, Polished Ruby.

1. http://roda.jeremyevans.net/index.html 2. https://www.packtpub.com/product/polished-ruby-programming/9...


I loathe most shortcuts: too many things to remember and usually cryptic syntax. Maybe that's because I have to be a polyglot developer (Elixir, JavaScript, Ruby, Python) and I can't remember all the tiny details of each language.

If you want to give a try to Ruby, get to understand blocks quickly. Instead of

  for item in list:
    work_on(item)
which would work (with a closing end and no : ), the idiomatic way is

  list.each do |item|
    work_on(item)
  end
or the one liner

  list.each {|item| work_on(item)}
Check also map, select, each_with_index, each_with_object, etc. Blocks are fundamental to Ruby for reason that only practice can tell.


Since "work_on" takes a single argument which is a instance of Item, you'd be better off implementing it as an instance method, so item.work_on. Then you have the even more compact

    list.each(&:work_on)


Or with numbered parameters (since ruby 3.0)

  list.each { work_on(_1) }


Swift has this as well, and it's really nice.

Doing map(lambda x: x + 1, stuff) or [x + 1 for x in stuff] adds nothing of value for those very simple cases. I wish we had map($1 + 1, stuff), or ever better, stuff |> $1 + 1.

Python does a lot of things right, but for a language with a strong emphasis on the iterator design pattern, I wish it would provide more expressive primitives.

However, it comes with the FP territory, which Python is not well suited for, and it would also imply a higher cost of entry to beginners.


Which is exactly the kind of shortcut I loathe.


you're not alone :) https://bugs.ruby-lang.org/issues/15723

This syntax looks ugly, but it's really nice when writing short scripts. Combined with one line function declarations: `def foo(n)= bar`, it can make an irb session much faster/shorter.


Quite horrible imo. Creeping closer and closer to Perl with each iteration


From what i've seen ruby is definitely on the downward slope regarding adoption and python has become a defacto standard in some fields. Its ecosystem is much better in pretty much every domain, so i don't think there's any point switching at the moment, honestly.


I've used both. I slightly prefer python over ruby. But I haven't used either recently.

Ruby's strength is internal DSLs. Python's strength is that it's simple to pick up for people. There's a large community of people that are not primarily programmers that use it. The reason for this is that it is a very easy language to get started with.

Both share the same problem: they are weakly typed scripting languages. Both have in common that type annotations have been retrofitted to the language. But it's an uncomfortable and awkward retrofit in both cases and their respective communities seem to regard using types as an optional and inconvenient thing. Ruby with types of course exists. E.g. Crystal is nice apparently. But I've not really seen that used in the wild.

Python is enormously popular; ruby's popularity seems to have declined a lot. I know a lot of former Ruby programmers that have moved to different languages and only a handful that still use it. It's not an obvious language to pick up for younger programmers. There are so many other languages to choose instead.

IMHO the key innovation in language design in recent years has been type inference. This has lead to a new breed of languages that keep most of what made python and ruby popular: the ability to write code with a minimum of verbosity but without sacrificing type safety. Whether you use Kotlin, Typescript, C#, or even Java: they have type inference now and it reduces the verbosity that comes with having types. Scripting vs. compiled languages is much murkier these days where scripting languages have compilers or transpilers and linters and compiled languages have REPLs and all languages can now run in a browser via WASM anyway. The distinction is just not that relevant anymore.

I write a lot of Kotlin, have dabbled a bit with typescript and I don't think I'll ever use languages without proper types again. Just not worth it for me. I don't see the value of not having a good typing system. Kotlin is interesting to me because internal DSLs are a thing with it; just like they were a thing with Ruby. And it can use javascript as a compilation target. Ruby has a slightly nicer syntax for DSLs than Kotlin for internal DSLs. But I don't mind curly braces and having strong typing enables a lot of good stuff. Like proper IDEs with refactoring, autocompletion, auto-fixes, etc. which are all very nice things to have in a DSL.

I use Kotlin DSLs for writing CSS, HTML components (we use kotlin-js), writing elasticsearch queries (I maintain the multi-platform kt-search library), gradle build files (which uses kotlin script), and quite a few more things. IMHO, it could do a lot of things that python is popular for as well.


> they are weakly typed

I think you should check the difference between strongly and weakly typed languages [1]

You probably intended dynamically typed.

[1] https://en.wikipedia.org/wiki/Strong_and_weak_typing


C# and Java with minimum verbosity? That I'd like to see. Kotlin.js HTML generation is just hideous.


[flagged]


The parentheses become less obtrusive when you indent lisp code properly. Then you just read it according to indentation like Python. I don’t find the parentheses problematic.


Good thinking. You might have to learn something new.


I've been using python for two decades and ruby exactly one decade ago for a couple years.

Your comment on packaging is interesting.

My perspective is that packaging on ruby was in nearly the same state ten years ago as python is now.

My team switched ruby package managers several times during the ruby work and none of them ever worked reliably and was a regular root cause of fragility in our builds.

I hope that ruby packaging has settled in that time while python has the same kind of cultural noise that afflicted ruby during that period. Popularity has downsides. Political packaging wars is one of them.

I'm old school enough that I stick with tried and true pip, venv, and .env and its ilk.

There was a long battle in downstream packaging for a while that I just didn't want to be involved in having seen the same thing in ruby.

FWIW, if I were you I'd look at crystal, elixir, or nim. I was very productive in ruby but the near constant battle of wrestling with DSL's that are not concomitant with their parent is, IMHO, to be avoided. I've seen more than one team fall prey to that trap. Caveat emptor - pick your DSL's carefully.


What do you mean by switching packaging managers? Ruby uses Rubygems and Bundler to manage packages.

Are you referring to Ruby version managers that manages different versions of Ruby (rbenv, rvm, asdf, etc.)?


They mention doing this a decade ago. They were probably using utilities to manage downloads from the ruby application archive (which would have still been around at the time) and automate running `setup.rb`


Yes, all of the above. Sorry for the unclear language.

At the time, I recall it being very distracting when the team was focused on shipping well-tested code. We had a world class devops guy, but we all ran into these build issues frequently enough that it was a regular complaint at retrospectives no matter what tooling we were using at the time or who was using it. It was not long after this that I read Daniel Greenfeld's book on Django that introduced the notion of "vanilla django" and that resonated with me, precisely because our rails tooling was decidedly not vanilla. My take now is that rails can have the same problems with regard to being "off the rails" WRT the aforementioned DSL's and such. I don't think the root cause is ruby as much as there are pockets of rails that are far from what DHH describes.


It really doesn't... After years using Ruby my mind never managed to parse its complicated grammar.


What does this mean, concretely speaking? Do you mean you can't remember the right syntax when trying to program in it?

I know its syntax is complicated but I never felt this has caused me trouble. I guess writing a parser for it would be extremely hard, but that's another story.


The optional parenthesis kills it for me. It makes scanning the code impossible: you have to understand each line context.


How long do you program in Ruby?

I understand this coming from a newcomer but after a while it felt like second-nature to me. I was curious about parent poster, because they said they are using Ruby for years.


How does this make it confusing? What information do you feel () gives you?

You might say:

      foo().bar().baz()
Ah well these are clearly method calls, they have ().

But this is Ruby:

      foo.bar.baz
These are clearly method calls too, because in Ruby there’s nothing else they could possibly be. The ()s don’t actually disambiguate anything. Unlike C or Java or some other languages with some notion of bare access to internal fields, there are always and only method calls.


Yeah, the optional parenthesis is one my biggest complaint with the language.

It just makes things less readable, with not other benefits than making the code look more clever than it is.


How? I’ve been writing Ruby and Rails for over a decade and I can’t really say it’s ever been a problem for me.


Any examples? I always found it easy to grok but I'm not at a very high level so I'm curious where it gets hairy.


There's a bunch of things to pay attention to in Ruby. I wouldn't say these get too hairy, but they trip students up for very understandable reasons.

* `a ||= b` is not just `a = a || b`, but `a || a = b`

* The distinction between defaults arguments and hash arguments in functional signatures has many edge cases -- but it's much much better in Ruby 3.

* There are still 2 pieces of syntax for hashes (`{a: 1}` and `{:a => 1}`) They are the same.

* Curly bases show up a fair bit for a language that's not a braces language. :) They're in hashes, blocks, and % strings. e.g. `%w{a b c}` is the same as `%w|a b c|`. They're also used in string interpolation, e.g. `"Hello, #{name}"`.

* There are sometimes many ways to do the same thing. There's Procs, blocks, and lambdas. % strings work with many characters.

* `unless` for `if not...` and get wonky.

* Overusing "surprise if" e.g. `do_thing if other_thing` can be hard to read.

* It's so easy to monkey-patch and extend primitive classes, you might not realize when libraries do it.

All of these are features that I actually find nice to use in the right contexts. But they can pop up when you don't expect, and they can definitely make Ruby a challenge to learn. That said with a little discipline (or just a linter ;)), I find a lot of Ruby code a joy to read and write.


> `do_thing if other_thing`

Yeah I don't get why you would deliberately make the flow control confusing and backwards like that. Python list comprehensions are the same. They make the information flow backwards.

This also has the effect of making nesting really confusing.


For me this fits more into how I think or how I say things in my mind.

It might be that I am not an english speaker, but for example when I say to someone a list of instructions I usually say it like this:

"Add more water if the dough is dry"

and I don't usually say

"If the dough is dry add more water"

The same for saying for example:

"stop mixing if the dough is starting to tear"

or

"put the bread in the oven when it is ready"


> > `do_thing if other_thing`

> Yeah I don't get why you would deliberately make the flow control confusing and backwards like that.

I like to use this as function guards, where the first lines in the body of a function can be:

  return xxx if some_edge_condition?
example: https://github.com/rails/rails/blob/f95c0b7e96eb36bc3efc0c5b...


Guard clauses are great, and when used appropriately clean things up. This is pretty much my only use of the pattern.

(Ending boolean-returning methods with a ? is also a great convention in ruby.)


if some_edge_condition: return x


I’ve written both professionally and personally found Python makes it harder to see the early out, which makes it less valuable/explicit as a guard.

The Ruby pattern is clear up front on what it is rather than being a bag of conditions that you have to get to the end of to discover what they trigger.


It's even worse when you combine the two complaints:

do_thing unless other_thing

Which of course you find in many Ruby codebases.

Python list comprehensions seem less painful to me because they read almost like the WHERE clause in SQL simple SELECTs. Definitely not the most straightforward thing one encounters in Python though.


I don't quite get the distinction in your first item: What's the difference between `a = (a || b)` and `a || (a = b)` ?


The rabbithole is here: https://stackoverflow.com/q/995593


wow, thanks. I worked with ruby for more than 5 years, and `||=` never surprised me.

TLDR On the distinction between `a = (a || b)` and `a || (a = b)`:

* There is no difference when `a` and `b` are local variables (unless `a` is undefined, but that's an edge case)

* The distinction manifests when `a=` is a method. (It could be a `[]=` or some attr_accessor). Then `a = (a || b)` always invokes `a=`, whereas `a || (a = b)` only invokes `a=` when `a` is falsey.

In essence however, `a ||= b` respects the intuitive definition that it sets `a` to `b` if and only if `a` is falsey.


Yeah, it's not usually an issue in practice. But I do see students get confused trying things out...and it's likely because learning in an interpreter doesn't often look like "real" code does.


Guido van Rossum put it this way:

"Again, the templating language seems a weird mixture of HTML and Ruby, and I find Ruby's syntax grating (too many sigils)."

https://www.artima.com/forums/flat.jsp?forum=106&thread=1461...

>Then I decided to have a look at Ruby on Rails, just to see what I could learn from the competition. I watched two fascinating movies, but they went a bit too fast to really understand what was going on, and there seemed to be a fair amount of sleight of hand in the examples (a lot of default behavior that just happens to do the right thing for the chosen demo). Again, the templating language seems a weird mixture of HTML and Ruby, and I find Ruby's syntax grating (too many sigils). I believe I heard Greg Stein say recently that if you are really good in Ruby, CSS, HTML and SQL, you can produce great websites quickly with Rails -- but if you don't, you produce lousy websites quickly (just like with PHP).

I quoted Guido's point about Ruby syntax 15 years ago in this lua-l discussion about Lua, Python, Lisp, and Ruby syntax, and the importance of not naming programming languages after naughty bits:

https://lua-l.lua.narkive.com/Ly6vITFE/justify-introducing-l...

>Stephen Kellett 15 years ago

>[...] A little story to explain why in house languages are bad:

>Years ago I worked for a company that had an in-house language (90-94) called Lull. [...]

>(*) Come be real, who uses Lisp for commercial projects? Great language, but too many parens and no one uses it. Its the equivalent of Esperanto. So much promise, so little delivery. [...]

>Don Hopkins 15 years ago

>I read your message about "Lull" and a Dutch guy sitting behind me laughed out loud when I said the name of your language. (My dictionary says "lul" means "prick" or "cock" in slang, so it's a bit more explicit than "dick"!) This just goes to prove that how you name a language is important (but not as important as designing the language so it doesn't suck...)!

>On the "too many parens" excuse for disliking Lisp: so why do you use XML and HTML? They have TWICE the number of parens, which are pointy instead of rounded, but lots of people seem to use them anyway. So "too many parens" is just a convenient and shallow excuse for disliking Lisp that avoids examining the real issue.

>The real problem that many people have with Lisp is because it's perceived as a homosexual programming language, which causes unconscious cognitive dissonance, so people have to grasp for more socially acceptable reasons for disliking the language (like "too many parens"). Nobody wants to just say "I hate Lisp because it sounds gay", so instead they say "too many parens", even though most other languages suffer from too few parens, and are much less readable than Lisp because of their insane hierarchies of precedence rules. Programming tips 101: ALWAYS use parens even if you're sure the precedence rules will make your expression do what you want, because 1) other people need to be able to read the code and 2) you may be wrong. Drop any copy of K&R on the table so it lands on the spine and opens to a commonly used page, and I bet it opens on the table of precedence rules, because that's the page everyone always turns to. Lisp code never suffers from this problem.

>The first thing most typical homophobic Americans think of when they hear the word Lisp is the gay stereotype of speaking with a lisp, so it unconsciously terrifies them. And the fact that "lambda" signifies unity under oppression, and is used as the gay/lesbian/bisexual/transgendered symbol only reinforces that impression: http://en.wikipedia.org/wiki/LGBT_symbol#Lambda ...And then there's the purple cover of Structure and Interpretation of Computer Programs, with the two gay dudes on the cover.

>Anyway, millions of people program in Lisp every day with C syntax, but they just call it JavaScript, and suffer without macros, because of the inferior C syntax.

>-Don

>Fabien 15 years ago

>@Don: your theory is fine for US people, but lack of love for Lisp is also observed in non-English speaking places. I learned quite recently that the word "lisp" also had another meaning than "nail clippings in oatmeal".

>My guess would rather be that Lisp doesn't encourage common idioms and development approaches across people and teams, which makes it hard to:

>- get into code you didn't develop

>- work with more than a couple of teammates

>- find a library that addresses your problem

>- if you have several problems and find a lib to address each of them, get those libs to work together despite their incompatible macro hacks.

>That, and Lisp took waaaay too long to acknowledge its platforms. It pretended to be OS independent, which meant many non-standard, non-compatible ways to interface with OSes, and therefore, plenty of platform issues as soon as you wanted to port or deploy a non-trivial solution. As written somewhere by Paul Graham, "Unix has won, get used to it".

>Don Hopkins 15 years ago

>Now that's some spot-on criticism of Lisp, much better than the "too many parenthesis" excuse that gets thrown around by linguistic homophobes suffering from cognitive dissonance.

>I'm still waiting to hear somebody claim that they refuse to use XML and HTML because they have too many parenthesis (angled brackets). Or for somebody who hates Lisp's parenthesis but tolerates XML and HTML to explain why <foo>bar</foo> is easier to read than (foo bar).

>Perl and C++ have way too much punctuation in general, and Ruby made a cargo cult design mistake in imitating Perl syntax. I agree with Guido van Rossum's comment on Ruby: "I find Ruby's syntax grating (too many sigils)". http://www.artima.com/forums/flat.jsp?forum=106&thread=14614...

>Wow, in what language does lisp mean nail clippings in oatmeal, and where can I get some of that stuff? It sounds delicious!

>"Using these toolkits is like trying to make a bookshelf out of mashed potatoes." -- Jamie Zawinski, on X-Windows toolkits.

>-Don

[...]

>Fabian: http://en.wikiquote.org/wiki/Larry_Wall look for "Lisp has all the visual appeal of oatmeal with fingernail clippings mixed in")

[...]


The problem with lisp is `lack` of sigils to give one enough sense of flow.

Moving the ( to the left of the function name somehow feels as disconcerting as gazing upon an non-trivial Haskell function and trying to map the type signature line to the function definition directly following.

Confusion is the mother of "Back to python, where I can knock this out."


To my eyes, sigils create turbulence, not flow. And they don't have obvious well defined standard meanings, and aren't self documenting or easy to look up in a manual or dictionary.

Who can even remember how ASCII punctuation should be "alphabetized" in an index, let alone Unicode?

However the pure parens of Lisp code look like concentric ripples on still water, instead of the raging rapids of pathologically punctuated Perl code.

Jack Kerouac and William Shakespeare and Hunter S. Thompson all managed to write beautiful laminar flowing text with words, without resorting to sputtering splashes and eddies of excessive punctuation.

https://www.quora.com/Who-are-some-writers-whose-texts-are-g...

Would you really want to read and maintain somebody else's quirky code written in a programming language designed by e e cummings?

The most difficult and important part of programming is choosing the right words and names, to help the reader understand your meaning and intention.

But there are only a few punctuation characters, unless you resort to inscrutable C++ digraphs, trigraphs, unicode, ambiguous smilies ;), and emojis. (Is that punctuation after the smilie a drool, or an Oxford comma?)

https://en.wikipedia.org/wiki/Digraphs_and_trigraphs

Do you really believe peppering your code with punctuation and emojis instead of using meaningful evocative words makes it "flow" better?

He was making an April Fools joke, but Bjorn Stroustrup's proposal for Generalizing Overloading for C++2000 would have actually improved the language's flow!

https://www.stroustrup.com/whitespace98.pdf

Edit:

My friend and brilliant Lisp hacker Ken Kahn (who teaches kids AI programming with the Snap! visual programming language) was just inspired to explore these stylistic visualizations of Lisp programs with Dall-E:

Ken Kahn:

https://scholar.google.com/citations?user=9hQiyqcAAAAJ&hl=en

Enabling children and beginning programmers to build AI programs:

https://ecraft2learn.github.io/ai/

A detailed painting of a Lisp computer program:

https://drive.google.com/file/d/1pYPNlhD6Q4gvGpjy_SUcuthz93i...

A detailed painting of a Lisp computer program in the style of Ceezanne:

https://drive.google.com/file/d/1SkWQmmpjOg_LhBW0xoEm_9FszXa...

A detailed painting of a Lisp computer program in the style of Van Gogh:

https://drive.google.com/file/d/16Mno-3mHUPPBmTaeM750SKLf8hd...

A detailed painting of a Lisp computer program in the style of Rembrandt:

https://drive.google.com/file/d/1czti3cQzv6P0SCCjCzyRx-NHMlI...

A detailed painting of a Lisp computer program in the style of Herman Brood:

https://drive.google.com/file/d/1EORrmLdH6jY-ShjyzauTvXZ39kW...

A detailed painting of a Lisp computer program in the style of Jackson Pollack:

https://drive.google.com/file/d/1Oszo7fqugGTFjpALR1Y9v4AIXwo...

A detailed painting of a Lisp computer program in the style of El Greco:

https://drive.google.com/file/d/1igFBKYAfGLBFxYPnNPp8UVbzyuA...

A detailed painting of a Lisp computer program in the style of Matisse:

https://drive.google.com/file/d/1-Q9jF0gbUXef86BiKEALf6FeA4e...

A detailed painting of a Lisp computer program in the style of Jan van Eyck:

https://drive.google.com/file/d/1N5l3depTIAwP8dCscZ87_Hm9v5i...


> Who can even remember how ASCII punctuation should be "alphabetized" in an index, let alone Unicode?

Ideally, the entries should mostly be one character long, at most two, and appear i a dedicated non-alphanumeric index section that runs for no more than half a page.


I am mostly brainwashed to read python, bash, and jquery.

Yes, I am a PB&J programmer.


I think with "shortcuts" they mean things such as $1 (first match group of a regex) or =~ (regex match operator).

I general I rarely feel like I am unproductive in Ruby (or bogged down in boilerplate typing).


In that case -- I definitely agree Ruby usually gets out of way. It's often my go to for hacking on something, unless it's data science-y.

I'll mention: implicit returns, return if, and safe navigation, the &. operator. Ruby's newer pattern matching features are really cool[1], especially for things like JSON structures, but I've only use started using them. Classes are also super easy to extend.

Many of these things are also incredibly easy to abuse - but they have their places.

[1]: https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rd...


I like the RubyMine IDE a lot. The code intelligence is very good – it’s so good that it tends to teach you Ruby as you use it. Code intelligence as in code navigation, easy access to documentation on the fly, refactoring, code style suggestions.

The same goes for the tooling integration. It works with most of the development tools you’d use with Ruby and captures the integration pretty well in a UI and associated workflow.

Link for your convenience: https://www.jetbrains.com/ruby/

(No affilition except being a paying user.)


I am using both Vim and RubyMine and I strongly recommend RubyMine for anyone starting with Ruby.

Having the possibility to have a Go to definition for almost any method is great and most of the gems have great code comments with a lot of nice examples.

When I teach people to learn Ruby (I do this sometimes) among the first things I do is I explain to them how to open a gem, look at the source code, read it and then try to use it.

I cannot thank enough the maintainers of the most popular gems for doing such a great job in having great ruby code and good in-file documentation.


Here's a rough list of benefits of Ruby compared to Python. It's not well organized and the examples are difficult to show in a comment box, but I have pages of notes of why Python is frankly a horrible language compared to Ruby.

Every expression returns a value, so you can assign the result of an if/else or switch statement to a variable. Within your conditional clauses, you don't have to repeat assignments or return values. If the logic goes down that branch, then the last value in that block is the return value.

This also means you don't have to have explicit 'return' statements in functions. Whatever the last value of the last expression is, that's your function return value. You can use an explicit return if you want to exit the function early, however.

Ruby has a switch (case/when) statement. Python finally has this in 3.11 I believe, but it took forever to get.

Hash.dig(). If you have nested hashes (dicts), you can myhash.dig(:top_key, :lower_key, :even_lower_key)

In objects, you can control which attributes can be read/written via attr_reader, attr_writer, attr_accessor. So you can make an attribute have a default getter if you use attr_reader :some_attribute, and then you can write your own setter if you want to control how it gets assigned to. You can even simply not have a setter, so you can effectively make an attribute read-only.

Clean, simple hash initializers: h = Hash.new { |h, k| h[k] = { age: 0, nickname: k } }. So now you can just start assigning to values in h like h["bob"][:attr1] = 28, and now h["bob"] refers to a hash with age 28 and nickname "bob". Yes Python has defaultdict, but it's an extra thing you have to use, and it's clumsy compared to the Ruby approach.

Hash (dict) merging: It's a mess in Python. In ruby it's just h1.merge(h2), or even h1.merge(key_to_update: 123). And you get a new hash as the result. Or you can merge in place with merge!(), changing the original.

Function names with ! and ? greatly simplify function naming (and improve readability). So instead of is_foo(), you just have foo?(). And if you want to indicate that you are doing something destructive, you name it foo!(). This is a common pattern in Ruby libraries, and you often have functions which return a new value and functions with the same name followed by ! which modify the input value. String.strip() returns a new string without leading/trailing whitespace, while strip!() modifies the input string to the same effect.

Python's list comprehensions are arguably ugly and messy and harder to visually parse when reading. Ruby has consistent ways of doing operations on collections. This would take a full page to show examples.

Copying and pasting code in Python often results in new code which is indented wrong (in the wrong scope), and which you must manually fix. This is not a problem in Ruby, since there are actual 'end' statements. Editors know how to properly indent what you paste.

Safe navigation. myobj&.someattr&.foo will give you the value of foo or nil if myobj or someattr don't exist. You have to use ifs to do this in Python.

Array flattening. nested_arrays.flatten(). But in python, you have to use something like list(itertools.chain(*arr)), but that fails if the first element of the list is an int (not an iterable).

Gettings hash/dict keys. In Ruby, you just do myhash.keys and you're done. But in Python there are several ways to get the keys, none of which is as simple and obvious as just keys().

Not allowing typos to silently fail. With Python, you can take an object and assign a value to a non-existent attribute. You might do this accidentally if you have a typo when trying to set an actual attribute: myobj.first_Name = "bob". Python will happily do that, and you'll later debug and wonder why myobj.first_name == "" (and then notice that you have this similarly named attribute now called first_Name). Duh. I guess you could say it's convenient to be able to do this, but it's more a footgun. Ruby will bark at you if you try this.

List sorting in Python is in-place, so you can't non-destructively sort and get the sorted list returned. In Ruby you have a choice. sort() is non destructive, and sort!() is in-place.

I've got more, but this is enough.


Using the sorted() built-in returns a new sorted array. Getting keys from a dictionary is literally just calling dict.keys(). Sorry, but I don't think you've used Python enough to offer lists like these.


I stand corrected on the mylist.sort() vs sorted(mylist). But really, such a mistake is more likely with two very different ways of doing the same kind of operation.

In ruby you would just mylist.sort() for a new sorted list (although parens are optional if no arguments, but I show them), or mylist.sort!() to sort mylist in place. You don't have to know two very different things. Plus it's obvious with the !

> Getting keys from a dictionary is literally just calling dict.keys()

Nope. Unless your definition of "keys" is a 'dict_keys' class. Chances are really good that what you wanted was a list of keys, not some object which does not behave exactly like a list. If you want to access the keys by index, you can't do it like mydict.keys()[0] because dict_keys does not have an index method.

Oddly, if you want a list of keys of a dict in Python, you just do list(mydict).

But you can also do [*mydict], which gives you a list of keys but is something special you have to learn.

Here's a fun one. Give me the first key of a dict.

Ruby: mydict.keys.first

Python: (several ways)

next(iter(mydict)) # which makes no sense at all... asking for the next value of something when you're actually asking for the first.

list(mydict)[0]

[*mydict][0]

What if you want the values of the dict? Well, no surprise, Python gives you a dict_values object when you do mydict.values(). Likewise, you can't do listy things with that because it's not a list. You have to convert it to a list with list(mydict.values()). Or you can do the [*mydict.values()] thing.

Python is full of so many inconsistencies, the cognitive overhead is much higher than it needs to be. I would argue that it makes people dumber, because they have to waste mental energy learning weird things for special cases.

But what about the rest of my previous post list?

Here's one I left out. The return-early pattern is common in imperative programming. We like to exit a function as soon as we know things aren't going a certain way.

In Ruby: x = fetch_object("bob") or return "bob not found"

In Python you need 2 or 3 lines (2 if you're using the walrus := operator).

if (x := fetch_object("bob") is None):

  return "bob not found"
How about setting a value for a variable if it is unset? This is a common thing done when you have variable arguments

Ruby: x ||= "default"

Python: x = x or "default" # not only does it look stupid setting a thing to itself, but it will fail if x is zero.

Edit: fixed asterisks.


Dict_values is a readonly set, not a list. Sets are generally unique, non indexable and unordered.

Since dict keys are generally intended to be unique, this is a much better data structure, because very often you just want to check if x in d.keys(), being a o(1) rather than linear search into a list -twice, because you first have to construct the list also!

It’s also more of a set-like view into the original key-set. This is why it shows up as dict_keys rather than set. You wouldn’t want every piece of code that simply does for k in d.keys() to first make a copy of the entire dataset before starting the iteration. Beginners will use these functions all over just like that, even though you can simply do for k in d. Or if k in d. Preventing such footguns is worth the inconvenience of having to convert to list(d.keys()) for the rare case of when it’s absolutely needed.

I’ve also never seen any code that need to get the first item out of a dict, what does that even mean? Dicts are mappings. Better optimize both syntax and performance for the more common use cases. Only use case I can see is an LRU cache but for that there are better ways, like @lru_cache or OrderedDict.pop. Where that’s needed, copying the whole dict into a new array would also be extremely inefficient.


> I’ve also never seen any code that need to get the first item out of a dict, what does that even mean?

You have a dict which contains a collection of similar values indexed by one value (which is the key). These may be records of people, and the key could be their phone number. Or it's documents indexed by some id, and the id is the key. Or orders keyed on order_id.

If you need to make a decision about processing based on some characteristic of the first item, you're not going to loop over the collection; you just need to know the key or possibly some element within the value of the first item in that dict.

Lists, dicts, tuples, sets, etc. are all collections. Why should collections not have as consistent features (and behaviors) as possible? Every collection has a first item. Maybe for unordered collections it doesn't make sense, but only set is unordered here.

It's nice to be able to go from general to specific, and to stay as high up that ladder as possible. And when designing systems that push complexity to the edges, the fewer ways of doing the same conceptual things, the better.

Python provides pop() for lists and dicts, and those both operate on positional data within their respective structures. Python could add first(), nth(), take(), and other nice conveniences to their collections. The low level implementation could be optimized, but the developer would have the benefit of common concepts.

Consistency matters a lot, because it means once we learn a concept or pattern we can apply that pattern generally without having to make a lot of changes for special cases.

For example:

h = {a: 1, b: 2}

h.map { |k,v| v * 10 } => [10, 20]

You could probably have guessed what that did even if you didn't know Ruby.

a = [1, 2]

a.map { |x| x * 10 } => [10, 20]

The only difference here was that we knew that each element was a single value rather than a key value pair (which we destructured into k and v in the previous example).

But let's do the same in Python.

d = {'a': 1, 'b': 2}

[v * 10 for v in list(d.values())] => [10, 20]

l = [1, 2]

[v * 10 for v in l] => [10, 20]

Or of course you could just do this:

list(map(lambda kv : (kv[1] * 10), d.items())) => [10, 20]


Half of the list is equally premature conclusions. Without entertaining this too much: You have properties, slots, __setitem__, dict merging with inplace h1.update(h2) or returning {**h1, **h2, “key”:”toadd”}. Defaultdict being clunky, really? Mypy does catch assignment typos outside __init__. Chain failing on non-iterables sounds expected, what else should happen? Implicitly treating as list of 1…? Explicit returns is a good thing, it avoids ambiguity and allows static analysis on missing return paths or returning wrong type.

Still got some gems in there that python could benefit from. Like safe navigation and nested dig, you can sort of emulate it with .get(“top”, {}).get(..) but it’s messy.


I’m not sure what “shortcuts” the author is referring, but perhaps…

    x = y || z  # if y is falsy x = z

    x = f ? y : z  # ternary operator

    %w{one two three}  # array of words

And so on.


Also:

    x ||= 1
    x &&= 1
and the various built-in methods from Array, Hash, Enumerable, String etc.


These all exist in python, though you use or/and instead x = y or z x = f and y or z you can also use inline if/else x = y if f else z 3rd one just seems like an array of strings? ["One", "two", "three"] Not needing quotes for single words saves some typing tho.


You should probably try some 10-minute Ruby tutorial to see if the syntax doesn't completely scare you off.


When I looked at Ruby (admittedly a long time ago now) everything was 10 minute videos. Is there any actual useful material on it now?


The default guide for a long time was the Pickaxe book, which solidly pre-dates the availability of 10-minute videos by several years. There are a fair few links here that you might find useful: https://www.ruby-lang.org/en/documentation/


There's ~50 short exercises/lessons here: https://learnrubythehardway.org/book/

I did them when I started out, and occasionally revisit as a refresher. It doesn't teach everything in ruby, but it absolutely gives enough to do just about anything you could want.


Ruby seems to have cemented itself in my head as one of my favorites. I’ve built a career from it and am eternally grateful.


> The usual-style comments on the line(s) above things (instead of docstrings below them) are used for generating docs.

This is another thing about Python that I find very unsettling and wrong.


On the surface, Ruby and Python are mostly equivalent languages.

Internally, memory management is done differently, for example.

Now add a magic of Ruby on Rails and ActiveSupport to one side and machine learning toolsets to the other.


Not just on the surface. See the ruby to python bytecode compiler: https://github.com/whymirror/unholy


I love Python and use it pretty much anywhere I can, especially as it's ubiquitous in scientific computing. Is there a good reason to switch to Ruby except to try something new?


I would claim that Ruby as a language is much nicer to use, however it has less support for data science and other things which Python is much more well known for. If you ported over some libraries, you might find you like Ruby more in the end.


My biggest gripe with Ruby is how much syntax is optional and the "accepted" conventions by the community are to leave out a lot of optional syntax.

The worst offender (in my opinion of course) is leaving out of empty parentheses on method calls. It makes it impossible to know if something is a property or a function. The argument is that it doesn't matter if it's a property or a function, but I feel like it makes it very difficult to reason about performance. If I know something is a function, I'll look at the function and see what the performance implications are. Where as if something is a property, I assume it's "free" to access. This doesn't hold when reading Ruby code, and I basically have to look up everything all the time.

Just my opinion as someone that had to switch between python/ruby/perl to maintain tools written during different decades.


> but I feel like it makes it very difficult to reason about performance

This seems like a form of premature optimization.

I mean, you're not wrong technically, but if you were exceptionally performance concerned, you probably wouldn't be using ruby.

If you're just regular performance concerned (i.e. you want to stop pathological behavior in the hot loop), you're really only concerned about 1-2% of your code. You wait until it's an issue, run a profiler over your code and optimize the couple of parts that are causing the issue.


What do you mean by “property or function”, though?

There are no properties or functions in my view—only messages. object.message sends message to object, which object may or may respond to.


It's the same in Python. Methods just happen to be callable properties.


All "properties" are functions too (methods, to be exact). There is no difference.

`attr_accessor` is a macro which defines two methods.


Always use parens. That's my rule.


As a non-ruby user, from my POV ruby is primarily related to webapps. While it's possible to use it for other types of software, nobody really does, except possibly people that already maintain large codebases of ruby webapps.


> There’s only one kind of list container (an Array), and it’s mutable.

What are the other kinds of list containers in Python? I only know of `list()`/`[]`, but maybe I just don’t realize that some other thing I use is also a list container.


I’m guessing they’re implying tuple, it’s pretty common to consider that type as “immutable list” (although personally I very much dislike this categorisation)


> although personally I very much dislike this categorisation

It’s not a categorisation, it’s literally the nature of the thing in Python. And while I agree that it’s distasteful the error was in the original introduction.

As things are, python tuples are immutable sequences with all that implies. They are iterable, indexable, sliceable, …


Immutability crucially allows for hashability, so you can use (some) tuples as dict keys.


array.array, as another commenter mentioned; numpy.array is popular for performance-oriented code (but not in the stdlib); collections.deque; queue.Queue; tuple; struct (though it's a bit of a pain to make one behave in an array-like way and is usually better to use array.array); heapq; and multiprocessing.shared_memory.ShareableList.

Out in pypi there are many additional esoteric list-like data structures: bitarray is one of my favorites, and there's also awkward-array, cyarray, tinyarray, fixedlist, blist, bigarray, sortedcontainers, and many many more.


array.array


“Python is bash for mathematicians.” -Michael Slavitch


Python is used for Web dev (Django), geography (all SIG), system components (part of Unix), pen testing (E.G: scapy), 3d modeling (Blender), sysadmin (ansible), etc.

I've been coded mostly in Python for 15 years and using the scientific stack is a very small parts of what people pay me of, if they ever do.


You're misinterpreting that quote as criticism of Python instead of praise. He's not saying that's the ONLY thing that Python is good for.

I've also been programming in Python extensively for at least 20 years, including web application servers like Zope and TurboGears, embedding and extending apps and tools with the Python/C API and SWIG like SimCity, Gnu Radio, Big World MMPORG, robotics, simulations, Blender, Houdini, user interfaces like Tkinter and PyGTK and Sugar, XML processing, OLE integration, BeautifulSoup web page scraping, image processing, character animation, speech synthesis, system administration, infrastructure as code, machine learning, etc.

Python is of my primary programming languages, and I love it.

It's a GOOD thing that mathematicians use Python instead of Bash!


Mathematicians never used bash. Most of them didn't even touched the command line 10 years ago.

I'm not the one misinterpreting.


It's pretty clear and not subject to interpretation that I never claimed mathematicians ever used bash, but just the opposite: they use Python instead.

Bash is an easy way to plug other code together, but it's terrible for math. Python is a much better way to plug other code together, and it's much better at math.


>I'm not the one misinterpreting.

Yes you are. Forget the technical debate for a minute, the failure is in that you've misinterpreted the semantics of:

X is Y for Z

Y in this instance doesn't mean a tool/service historically used by Group Z, it's to illustrate that tool Y helped another unlisted group immensely.

Hence, the typical "this tool is Uber for..." way of trying to help people understand a startup.


If your argument requires me to "forget the technical debate for a minute" (and why should I?), and also you're nit picking at semantics with your own arbitrary definitions and personal interpretations, without any links or citations to back yourself up, you're really out on a limb there.

Do you really believe that I don't know what Python is good for after using it for over 20 years, working with clients including mathematicians, game designers, ai researchers, and my own personal open source projects like SimCity/Micropolis?

https://github.com/SimHacker/micropolis/tree/master/Micropol...

https://github.com/SimHacker/micropolis/tree/master/Micropol...

https://github.com/SimHacker/micropolis/tree/master/Micropol...

https://github.com/SimHacker/micropolis/tree/master/Micropol...

How many years have you been using Python for, and for what? Have some links you want to share to prove what you're saying?


I'm responding to the other person. Hence why the first line of my post is quoting them.


I'm missing the best part - In ruby we have an intuitive and extensive standard library


Of all measures to compare by that seems a particularly ill choice. One of the few things I know about Python is its longstanding reputation for having batteries included.

(But yes, you can pry Enumerable from my cold, dead hands.)


Forget all of it and use Go :)


The only thing I disliked more than moving from Ruby to Python was moving to Go. All the things I love about Ruby are the antithesis of Go.


Probably not a bad advice.

Most of the ruby enthusiasts I know from the old days took a deeper dive into go.


I would think Elixir or Crystal would have been the more natural transistion for Rubyists. Or even Common Lisp. Go's philosophy is very much at odds with Ruby's.


Ruby eventually failed in term of quantity of developers, means OOP will eventually died. It's not Ruby's fault, it's just the failure of OOP in general.

My prediction, in about 4 years or more, OOP is just an abandon concept for all programmers.


Mind you that (let's say) the modern concept of OOP was invented around second part of 60s (with Simula I think) and I think functional a bit earlier maybe in the beginning of 50s (with LISP I think).

Both paradigms have a lot of years of practice and saying that anyone of them will die in 4 years is I think a bit of exaggeration.

Why do you think that in 4 years OOP will be abandoned by programmers? What is the basis for your assertion?

I think it is more probable that we will have a kind of cross-pollination between these two paradigms with maybe newer languages (or evolving existing languages) to borrow concepts and best practices while still providing the core way to design solutions. This is already happening in smaller scale in multiple languages as far as I see it.


Uh huh. And static typing will replace dynamic for all programmers, functional will replace all uses of imperative, nobody will use whitespace indentation, and all emacs users will switch to vim. Also, every programmer will abandon Windows and macOS for Linux.


So, C++ and Java have just a couple of years of life left?


Not too mention C#, Python and for some, Javascript.


All doomed. Google and Amazon, for example, will rewrite their many millions of lines of Java and C++ in Haskell by 2026.


Most of the non-system languages on the rise (Kotlin, Swift, Dart, JS, Python, C#) are a mixture of OOP and functional so I don't see OOP dying anytime soon.


deleted


What do you mean?

> When tested for truth, only false and nil evaluate to a false value. Everything else is true (including 0, 0.0, "", and []).

In ruby only `false` and `nil` evaluate to false values. 0, [], "" all evaluate to true when used in `if` statements. In python (just like in C and JS) many things evaluate to false (including 0, 0.0, "", and []).


Ah sorry I read "Unlike Ruby, in Python" instead of "Unlike Python, in Ruby"


Really the only reason to use Python is ML, but that is the future of everything so….


> Really the only reason to use Python is ML

Not the other scientific and statistical libraries?

> but that is the future of everything so….

So is fusion and flying cars. Maybe one day AGI will be here, but I doubt we humans will be using Python ML libraries at that point.


In the next 5-10 years, ML will likely write most of our code and that will almost certainly come from Python


nah. ML is not the future of everything. in fact I believe that as things are going to evolve ML as we see it today is going to be only a tiny fraction of what is going to be used


yeah but the ML of tomorrow will write all of the code


>[In Ruby] There is good support for functional programming with first-class functions, anonymous functions, and closures.

This seems like a blatantly false claim to me since as a Python programmer forced to use Ruby for Sketchup, I have been frustrated by the lack of first-class functions in Ruby.


I’m interested to learn what “first-class functions” means to you. Ruby has both `Proc` and `Method` which would appear to qualify. Is your frustration that you can’t `def foo … end` and then use bare `foo` to refer to it as a value (vs invoking it)?


My frustration is that you can't pass foo as an argument to another function.


Right, I see, because you have to say `method(:foo)` or `-> { foo }` or whatever. To me this is an acceptable syntactic trade-off in exchange for being able to invoke the method with bare `foo` (i.e. variable reads and method calls look the same) but I can see from other comments here that some people dislike that ability anyway. Vive la différence!


But you can. Method objects, proc objects, lambda objects, blocks, you've got a wealth of options here in Ruby.


One of my biggest complaints about Ruby is that that it’s so much harder to drop into a debugger. In python it’s as easy as import pdb; pdb.set_trace(), but for ruby (at least the versions on our build systems), you need to figure out how to add the dependency for pry/byebug first which is non trivial.


A fair complaint, but FYI it’s outdated as of Ruby 3.0 (released in 2020) which includes the `debug` gem [0]. So in Ruby 3.0 onwards it’s as easy as `require 'debug'; debugger` just like you want. Hopefully your build systems will upgrade to a newer Ruby before too long.

[0] https://stdgems.org/debug/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: