Hacker News new | past | comments | ask | show | jobs | submit login

> 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.




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

Search: