To Ruby from Python
ruby-lang.org
ruby-lang.org
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.
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.
method(:blah_changed?).source_location
is your new best friend.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.
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.
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.
Just curious, annoying relative to what language/pattern that does it better?
Since golang is typed and uses dependency injection, its very easy to know what inputs you need or mocking out values.
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 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.
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.
Fix the last one
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.
Does it work with dynamic method definitions? ‘define_method(“#{req.id}_log”)’
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.?
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.
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.
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.
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.
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 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 :)
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 ;-)
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.
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.
What gem(s) were giving you issues? I can try and replicate it if you’d like.
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.
No need to vendor gems.. Vendoring millions and millions of duplicate files is for the node crowd.
and PHP
No need to vendor gems..
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.
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/ruby-package
IIRC there's an issue somewhere at bugs.ruby-lang.org about that
EDIT: OMG 8 years ago! /me screams
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.
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.
Wouldn't this be "import bar; 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.
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.
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.https://github.com/fxn/zeitwerk
It is pretty easy to set it up in any Ruby project.
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.
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.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.
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.
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.
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?
> 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…
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.
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.
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.
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.
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
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.
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.
If anything autoloaders enforce that constant names match filenames, making it easier to locate the source.
[1]: Archived in 2006: https://web.archive.org/web/20060925032432/https://www.ruby-...
not sure if python has better matching though but the ruby stuff is pretty compelling.
## 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.
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…’
Python’s the more frequently used of the two industry-wide, but it seems like they both won, just with different crowds.
Python is fine, the metaobject model is pretty nice, and I've stopped noticing the double underscores ;)
https://leanpub.com/rubyisforfun/ - English
https://leanpub.com/rubyisforfun_ru/ - По-русски
(if you found typos, you can update the manuscipt https://github.com/ro31337/rubyisforfun)
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.
No idea what else might be the reasons to learn Python when you know Ruby.
I've been using Python for a long time, but somehow I never learned about pydoc. It looks like a pretty handy tool!
(By that simile, Lisp would be Sanskrit?)
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?
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?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. >>> import calendar
>>> calendar.SATURDAY
5Also: https://github.com/search?l=Ruby&q=Date.today.saturday%3F&ty...
One incredibly common alternative to a codebase is a script.
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.
if date.today().weekday() == 5: print("It's Saturday")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)
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.)
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'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...
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.
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
}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?
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.
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 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.
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.
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)
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 :)
How about optional dots in Scala?
if (Day today saturday) println("It's Saturday")It presents OOP in Ruby in such a beautiful way and it takes a while to start thinking like this.
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.
That's like using * imports in Python which is a huge pain once you done even minor refactoring.
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.
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.
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...
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.
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.
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.
Rails is really full stack. Django's frontend story is a joke.
Look into Rack and Sinatra first, then Rails. Even if it's a short trip it's going to be worth it.
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...
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. list.each(&:work_on) list.each { work_on(_1) }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.
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.
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.
I think you should check the difference between strongly and weakly typed languages [1]
You probably intended dynamically typed.
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.
Are you referring to Ruby version managers that manages different versions of Ruby (rbenv, rvm, asdf, etc.)?
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.
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.
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.
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.It just makes things less readable, with not other benefits than making the code look more clever than it is.
* `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.
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.
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"
> 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...(Ending boolean-returning methods with a ? is also a great convention in ruby.)
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.
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.
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.
"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")
[...]
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."
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...
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.
Yes, I am a PB&J programmer.
I general I rarely feel like I am unproductive in Ruby (or bogged down in boilerplate typing).
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...
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.)
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.
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.
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 argumentsRuby: 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.
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.
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]
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.
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. x ||= 1
x &&= 1
and the various built-in methods from Array, Hash, Enumerable, String etc.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.
This is another thing about Python that I find very unsettling and wrong.
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.
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.
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.
There are no properties or functions in my view—only messages. object.message sends message to object, which object may or may respond to.
`attr_accessor` is a macro which defines two methods.
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.
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, …
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.
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.
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!
I'm not the one misinterpreting.
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.
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.
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?
(But yes, you can pry Enumerable from my cold, dead hands.)
Most of the ruby enthusiasts I know from the old days took a deeper dive into go.
My prediction, in about 4 years or more, OOP is just an abandon concept for all programmers.
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.
> 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 []).
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.
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.