Hacker News new | past | comments | ask | show | jobs | submit login
Ruby vs. Python comes down to the for loop (softwaredoug.com)
373 points by softwaredoug 69 days ago | hide | past | favorite | 302 comments



This was definitely written by a pythonist! If I tried to write it, as a rubyist, I'm sure I'd get some things about python wrong. (I find it notable how few people there are that are actually familiar with both).

The standard alternative to `for` in ruby does involve `each` and blocks... but definitely doesn't involve defining a custom `each` method on your class... That is a specialty thing that most developers have probably done rarely. Let alone defining it in terms of `for`, which is just weird!

But the basic principle stated "Instead of passing data back to the for loop (Python) you pass the code to the data (Ruby)" -- is more or less accurate.

blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby.

> Python builds on for-like constructs for all kinds of processing; Ruby pushes other kinds of data processing work to methods.

OK, maybe, although not totally sure what you mean.

> Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below:

Um. I'm not sure where the author is getting this. It is certainly possible, as shown, but definitely not common to implement `select` or `map` or other methods provided by Enumerable directly on your custom class. It is a bit more common to implement `each` alone and let the `Enumerable` mixin provide the rest. But even that I'm not sure how "common" I'd call it.

> Ruby, however, inverts this. Ruby puts object-orientation as the foundation of the pyramid. Ruby contains the messy procedural world in blocks, letting objects work with those procedural blocks.

OK, true. The author is getting the details wrong, but I guess their overall picture is still right?


> blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby.

Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values. Instead the language is complicated by special syntax and functions for sending and receiving blocks, and bizarrely limited by the inability to do anything with a block literal other than send it.

Blocks were so close to being good. They managed the triple flip with a double twist, but they couldn't stick the landing. It's not quite a faceplant at the end, but it clearly shows how much better they could have been.


What do you mean by inability to do anything with a literal? You can capture it, turn into a variable, turn it into a "lambda" (make next, break, return local), use it like callbacks are used in any other language. Blocks are a bit special in that they go in their own slot for message sends (method calls) which allows the syntax to be unambiguous and the yield keyword allows for optimized calls to passed in blocks because methods that don't reference a block as data don't have to worry about the block outliving the stack frame.


No there’s special methods which turns blocks into objects, and there’s a syntax to do that, but blocks aren’t objects unless you specifically do one of those things. The idea you’re replying to is that they’d be objects always.


  def proc &p
    p
  end
is what the special method looks like but yeah they’re not standalone expressions but rather part of the method call syntax.


> Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values.

Abstractly, I kind of see the point, in practice, I don't see it makes much difference, and given Ruby’s two flavors of function-like objects, it seems to work out for the best.

> the inability to do anything with a block literal other than send it.

But sending lets you do anything else you’d want to to do with it. Specifically, to get either of the flavors of callables, you pass it to “proc” or “lambda”, and then use the result.


Not having every block be a separate value makes it easier for the Ruby VM to optimize such code without escape analysis (which is something that's pretty hard to do in this language).

Don't have to allocate the container => don't need to use the heap one more time. Nor access the block's code through that indirection.

Same thing with methods: they aren't objects, but you can create an object pointing to a method any time you need one.


You have that with procs and lambdas


> Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values. Instead the language is complicated by special syntax and functions for sending and receiving blocks, and bizarrely limited by the inability to do anything with a block literal other than send it.

Blocks are syntactical structures. Your statement is like saying that the parens and commas that are part of the argument list should be "regular values".

I'm not sure what you want to do with a "block literal". If you want to do something with the closure represented by the block, then reify the block into an object to pass it around, wrap it, introspect etc.

I think a lot of confusion about blocks in Ruby is really inconsistent terminology. Using "block" to mean the syntactic expression of a closure, i.e. part of the method call syntax I think helps to disambiguate the syntactical nature of closures from the closure reified into an object that you can pass around, call, etc. (i.e. an instance of the Proc class)


> Blocks are syntactical structures. Your statement is like saying that the parens and commas that are part of the argument list should be "regular values".

Well, no. Know what else is a syntactic structure? Numeric and string literals. People would never have touched the language in the first place if Ruby required you to write code like

    x = String("abc")
    y = Integer(123)
But people bend over backwards to explain why

    z = { |x| x + 1 }
Is bad and shouldn't be allowed.

What?

The whole block vs proc thing is an artificial distinction. It doesn't add value, it removes it.

(Yeah, I know about the difference in how they handle return. This strikes me as an incredibly ad-hoc way to address something they could have solved a lot more elegantly.)


> But people bend over backwards to explain why “z = { |x| x + 1 }” is bad and shouldn't be allowed.

Really? I’ve never seen anyone bend over backward to explain it as bad, mostly just that that's the way it is, there are tradeoffs either way, and its not worth changing.

> The whole block vs proc thing is an artificial distinction

Presumably, you mean lambda vs. proc. (Both of which are defined using blocks, and procs are what using & syntax in a function signature causes a passed block to be reified into.)

But, yes, all distinctions are creations of humans, especially all distinctions within human creations like, say, programming languages. So “artificial distinction” is a meaningless descriptor when we are talking about things in a programming language.


Would it be fair to say you don't want to write this?

  z = ->(x) { x + 1 }

  z.call(y)


I don't see why there is this distinction where lambdas have to be called differently than functions.


Ruby doesn't have functions. It only has methods. That blocks in MRI happen to be implemented without reifying an object is an optimisation. You can only ever obtain a reference to said block by reifying it into a Proc instance.

Letting you obtain some kind of raw reference to a block that isn't a method on an object would make blocks unlike every other value in Ruby.


> I don't see why there is this distinction where lambdas have to be called differently than functions.

Ruby doesn't have functions; things that look like bare (non-method) function calls in other languages are just method calls on self.

So procs/lambdas can't be called the same as, or differently from, functions—they are the closest thing Ruby has to functions to start with.


Hah, as a Pythonista, I'd say it was definitely written by a Rubyist :D

That initial example would usually be, in Python:

  class Stuff:
    def __init__(self):
        self.a_list = [1,2,3,4]
    def __iter__(self):
        for value in self.a_list:
            yield value
Basically, iterators and generators are native constructs that for loop operates on in Python (lists, which are actual "data", are simply special-case optimized instances of those).

Instead of calling iterators/generators "data", I'd call them wrapper control-flow constructs that `for` really operates on which provide amazing syntactic power in Python.

Ruby, from the examples given, seems quite similar, except that the "syntactic sugar" is somewhat inverted. I don't think it makes for a huge difference, but I don't have any Ruby experience.


Or, in Python 3.3 and above:

  class Stuff:
    def __init__(self):
        self.a_list = [1,2,3,4]
    def __iter__(self):
        yield from self.a_list


Can't you just:

    def __iter__(self):
        return iter(self.a_list)


I was not trying to go for the shortest code, but to show idiomatic code where you could easily do something else instead of just returning same values (otherwise, there is no value in wrapping a list with a class at all).

With both `iter(self.a_list)` or `yield from` you'd have to add a list comprehension in there to process each element, and with no Ruby-like blocks in Python, that limits what one can do.

But they are both definitely good approaches to highlight!


You mean generator, not list comprehension. Either way, you wouldn't `yield from` a list comprehension, you'd just use a loop.

E.g. why write:

    yield from (a*2 for a in self.a_list)
When you can:

    for a in self.a_list:
        yield a*2


Exactly, that's the reason I used a loop in the original snippet :)


Author here, I think this would have made the post better for sure, especially to contrast how `yield` does the opposite thing in each language


The article has some misunderstandings about idiomatic ruby imo.

> Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below:

And then you show map, select, etc being re-implemented.

But once you have "each" defined, you'd just include "Enumerable" and get all the others for free.

As a separate point, I almost never implement each on a custom object in ruby. There are probably "library code" cases where it's appropriate, but in day to day work it should be rare. Typically I'd put objects in an ordinary array instead.

Nit: Ruby style guide (and most experienced code I've seen) never uses parens to invoke a method with no args.


    As a separate point, I almost never implement 
    each on a custom object in ruby. There are 
    probably "library code" cases where it's appropriate
Likewise. I've worked with Ruby fulltime since 2014 and never done it in actual project code, only in book exercises, and I'm not sure I've seen it in any gems I've dove into, though I've never poked around in ActiveRecord.

Like you said, I can't think of too many reasons why one would want to implement #each -- on a daily basis I'm just putting various objects into arrays, hashes, etc.

I tend to write very "boring" Ruby. Ruby gives you lots of ways to get wacky, but IMO the default best practice would be to keep it simple and avoid the cute stuff unless you really have a reason.


I didn't think the author was saying you should always implement a .each method. I thought the implementations were for demonstration purposes, that .each is a method like any other that can be overridden.


that's how i read it too, for the purpose of the technical demonstration


There are other details that are wrong. Ruby does have iterators — except they're called enumerators. The standard implementation is called Enumerator:

    >> [1, 2, 3].each
    => #<Enumerator: [1, 2, 3]:each>
    >> e = [1, 2, 3].each
    => #<Enumerator: [1, 2, 3]:each>
    >> e.next
    => 1
    >> e.next
    => 2
    >> e.next
    => 3
    >> e.next
    Traceback (most recent call last):
            2: from (irb):17
            1: from (irb):17:in `next'
    StopIteration (iteration reached an end)
Enumerators behave similarly to generators in Python thanks to utilities like #to_enum:

    class X
      def each
        yield 1
        yield 2
        yield 3
      end
    end

    >> x = X.new.to_enum
    >> x.each
    #<Enumerator: #<X:0x00007fd30c93dfa0>:each>
    >> x.next
    1
    [etc.]
Ruby's for loops actually use enumerators, just like Python. It's just not used as much, for cultural reasons; most devs these days favour Ruby's data-oriented inversion of control.


Python and Ruby are quite different when writing code, but have almost the exact same technical abilities and limitations in the grand scheme of things. I've always felt there's little reason to learn both.


Ruby is way more capable and expressive than Python. There is so much more you can do with Ruby metaprogramming and don't get me started on Python's feeble lambdas which bear the mark of a BDL imposing his distaste for functional programming. Python is the VHS of programming - widely adopted but technically inferior. Ruby appeals to devs who value elegance of design. Take Sonic Pi (https://sonic-pi.net), for example - I can't imagine sam Aaron producing anything like this in Python. The DSL is everything in this app as with Rails.


Not sure how they compare feature-wise, but take a look at FoxDot:

https://github.com/Qirky/FoxDot


After experiencing Sam Aaron's Overtone - a Clojure REPL for SuperCollider - this does not exactly inspire:

    d1 >> play(P["x( x)  "].palindrome().zip("---[--]").zip(P["  o "].amen()))


Yes exactly, you're not really going to learn anything (syntax aside) by picking up the second, unless you needed it for work reasons or whatever there's surely several other paradigmatically different languages that will be more interesting/instructive to learn.


I'd add elixir to that list too. For the most part, the language differences are not really capitalised on.


> but definitely doesn't involve defining a custom `each` method on your class...

Author here! Thanks for the feedback.

I suspected as much, and was more or less writing it to be illustrative. Though I agree I am bad at Ruby :)


I don't think jrochkind is correct at all, if you want to make a library that has a datastructure that you want to implement custom iteration on, then it is most definitely idiomatic ruby to implement `each` for it. Your article was spot on in my opinion.

That said, in the almost 15 years I've been doing Ruby as my preferred programming language, I think I can count the amount of times I implemented a custom `each` method on one, maybe two hands.

As an example of how `each` is idiomatic, consider the Enumerable mixin: https://ruby-doc.org/core-3.0.2/Enumerable.html if you implement `each` on your class that mixin gives you all those methods (such as select) for free.


Yeah.. totally.. 10 years, I've never seen a custom 'each'


> syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument

I think Perl has this too? Or, well, I should say that Perl allows you to do anything, so even if Perl doesn't let you do it, you can still do it in Perl.


It's the norm in Scala too.

And it's a bit overdone in (older) JS (libraries).


I don't know scala, but the notable thing about ruby vs JS is how ruby provides _syntactic_ support for passing an inline-defined function.

In JS:

    someObj.someMethod(function(something) {
       something
    });
Compare to ruby with the block arg:

    someObj.someMethod do |something|
      something
    end
That `do` is a special syntactic thing avaialble for passing a single inline-defined closure arg. (You can pass one held in a reference instead of inline-defined, but it actually takes an extra somewhat obtuse step -- the syntax is optomized for inline-defined).

This "affordance" says "Yes, we make it really easy to do this, the stdlib does it a lot, please consider doing it all the time in your own code", which goes along with some of what the OP is discussing. Why we write `collection.each {|something| something}` (the braces are an alternate way to pass a block) instead of `for something in collection do...`


I think the crucial difference is that a block can control the flow of the enclosing frame, somewhat analogously to python context managers. For example

  def get_widget
    with_lock do
      return @widget # returns from the method call
    end
  end

  def update_interesting_gadget
    @gadgets.each do |g|
      g.with_lock do
        if g.is_theone?
          g.update
          break # breaks the enclosing each's while loop
        end
      end
    end


So Anonymous Ruby blocks act like (or are) Procs (which return from the parent method), not like lambdas, would you say that?


In modern JS wouldn't you use an arrow function?

  someObj.someMethod(something => something);


The main advantages of Ruby blocks over that approach are:

- they have special control flow that interacts with the method call or surrounding function. ie. calling `break` in `something` can early return from `someMethod`, or calling `return` will return from the function containing the `someMethod` call (blocks use `next` to return from them)

- due to using separate syntax / being a separate language construct, there is far better ergonomics in the presence of vargs or default values

Take this contrived example for instance:

  def some_method(a = 42)
      b = yield
      puts "Hey #{a} #{b}"
  end

  some_method do
    break
  end

In JS you would have to something horrible like this:

  const breakSomeMethod = {}; // Could alternatively use an exception

  function someMethod(one, two) {
    var f, a;
    if (typeof one == 'function') {
      f = one;
      a = 42;
    } else {
      a = one;
      f = two;
    }

    var b = f();
    if (b === breakSomeMethod) {
      return;
    }

    console.log(`Hey ${a} ${b}`)
  }

  someMethod(() => breakSomeMethod);


In Scala it's called blocks [0], and if a function/method expects one you can provide it inline, eg:

    myList.foreach { x => doTheThingToTheThingEvery(x, 100 millis) }

Of course it's sometimes a bit too much. It starts out cute [1] and handy [2][3][4], then [5] ... [6] :)

[0] https://docs.scala-lang.org/tour/basics.html#blocks

[1] https://scastie.scala-lang.org/tpfgE80WTc6QQaHslraJag

[2] https://www.playframework.com/documentation/2.8.x/ScalaActio...

[3] https://stackoverflow.com/q/50370202/44166

[5] https://www.playframework.com/documentation/2.8.x/ScalaActio...

[6] https://miro.medium.com/max/784/1*EMiSTuRIxUPCzWOgLkiXoA.jpe...


Scala doesn't really have a name for this, it simply permits you to provide a block in lieu of a parenthesized argument list. Given that blocks are expressions:

    val a = { val x = 2; x + 1 }
the syntax you are describing is just a function-valued block.


as a rubyistpythonista citizen, i agree


While people have pointed out that this is not idiomatic:

  class Stuff
    def initialize
      @a_list = [1, 2, 3, 4]
    end

    def each
      for item in @a_list
        yield item
      end
    end
  end
I haven't seen anyone point out the deepest reason why: unlike in many languages, “for” is not a basic, low-level construct in Ruby, it is syntax sugar. That is:

  for x in y
    x.stuff
    more_stuff
  end
is sugar for:

  y.each do |x|
    x.stuff
    more_stuff
  end
(Also, for quite a long time, for...in also had runtime overhead , so it was slower syntactic sugar.)

for...in is rarely used (it is syntactic sugar that is neither more concise bor more specific in intent, but more familiar to people with experience in non-Ruby languages), and mostly on scripts that consume but don’t implement collections. If you are getting deep enough to be implementing an #each method for a custom collection, that's an odd place to use for.


But that actually makes it more similar to Python.

In Python, `for value in iterator: x(value)` is syntactic sugar for (roughly)

  while True:
    try:
      value = next(iterator)
    except IterationError:
      break
    x(value)
Basically, it seems that `while conditional` are both low-level control flow constructs in both: how do they differ between languages?


Ruby's “for … in” is syntax sugar in the sense that is just another way to spell an identical piece of code. This would be like if Python let you write “foreach” instead of “for” if you wanted to. Another example in Ruby is how you can say “unless” instead of “if not”.

Python's “for … in” is syntax sugar in the sense that is an abstraction over constantly writing out the iterator protocol.


If anything I found Ruby's breadth of syntax sugar a defining characteristic of it - you can express the same program in a million different ways based on the programming paradigm you wish to emulate, or even make a DSL of your own to express the feelings in your heart. Not that I think that's necessarily a good thing though...


I'm not sure if this was ever changed, but I remember running into something surprising regarding variable scoping with for loops (I think it was during an internship in 2014, so I think Ruby 2.x with some relatively low value for "x"). Unfortunately I can't remember exactly what it was; I think it was something about variables defined in the for loop still being in scope afterwards? That doesn't seem like it should be the case if it's just syntactic sugar for `each` though, so I might be remembering wrong


Yes, for loops (and similar statements) don't introduce scope, so its not quite simple sugar for .each, and there are a few cases where you might use them over each for that property (if you are mutating values that you want to access afterward, it can be more concise than each) but they are mostly anti-idiomatic.


For me, its the explicitness in Python that is killer. I can see a name and match that to an import and match that to a package. Grabbing a function pointer works exactly as I would expect.

Stealing from Krister Stendahl's laws for religious discourse, Ruby's composable iteration is one area I have holy envy, particularly after I got used to it in Rust. I've used Python's generators many times just to have to switch to an explicit for loop. Things are generally better with a composable iteration model but occasionally I find myself switching to explicit loops in Rust.

Unlike Ruby, Rust does pull-iteration like Python, though there are experiments with Ruby-style push-iteration [0] [1].

[0] https://github.com/AndWass/pushgen

[1] https://epage.github.io/blog/2021/07/pushgen-experiment/


> I can see a name and match that to an import and match that to a package.

I miss this so much. I'm a python dev now working on a big Rails monolith. Where does all this stuff come from?


If you ever wonder where something comes from and you can't get your editor/IDE to tell you, you can always try asking the runtime.

Drop a debugger statement in above the code in question, then at the prompt turn the method into a Method object using something like:

  bar_method = foo.method(:bar)
and then

  bar_method.source_location

Once you have a Method object you can pass it around as a stand alone function, rebind it or call too.

#source_location only works for methods defined in ruby itself i.e. not C or Java but it's still a handy tool on big ruby projects.

Most of the time I just use ctags to navigate new codebases but we also have things like Solargraph via LSP too.


Drop a debugger statement in above the code in question

If you're using Pry as your REPL, you can use Pry's ls and show-source commands to get more of the info you want at once, with less typing. It's basically calling the Ruby introspection methods for you.

    ls foo
    show-source -d foo.bar


"pry" is absolutely essential. I often prototype my code with a simple helper script that gives me a function to reload the relevant code, and just "live" in the pry prompt to explore the code as I add it.

It's the closest thing you get in Ruby to a Smalltalk environment.


> you can always try asking the runtime

90% of the time, I'm trying to read code in a very local context and understand it, which involves being able to trace an identifier to its source. I don't want to run it. Maybe I can't run it (it's a rarely executed path, the runtime env is complex, someone emailed me a copy of a source file, etc.).

Language developers, be like Python. Be like Java. Be boring and explicit.


You don’t deserve the downvotes here. Real life Ruby is so magic, opaque, and spooky action at a distance to a degree that makes maintenance on a large codebase functionally impossible for anyone who is not actively working on that codebase.

I know literally zero Go; don’t even think I could write a hello world from memory. But I can and have submitted non-trivial patches to Go codebases to fix bugs, add features, and even a few race conditions.


Having to use a debugger to determine the types of variables at runtime is just so terrible and backwards. I've had to debug massive python projects that have used monkey patching in the framework and it was terrible. This thread is actually causing me pain and making me relive horrible bugs i've had to fix. Reading about auto loading and having to use a debugger to inspect types is just so fucking bad.


I use Rubymine and can cmd+click on anything and it shows me exactly where it came from. Been developing in rails for a decade and the auto loading has bitten me at most 5 times.


Onboarding to all but the most perfectly-written-and-maintained Rails codebase is pure hell, for exactly that reason. I've done lots of Rails in the past, but sworn it off after enough such experiences.

[EDIT] Which sucks, because I really like Ruby.


Here it is important to notice that Rails is like a superset of Ruby. Rails = Ruby + conventions + helpers.

So if you want to always be aware where things come from choose Sinatra, Padrino or Hanami.


Are you using a good IDE? With Rubymine (which indexes all of your gems) this is a much less big a deal.


> I can see a name and match that to an import and match that to a package. Grabbing a function pointer works exactly as I would expect.

Autoloading is one of the worst features ever introduced into a language/framework.

https://erock.io/2021/11/01/on-autoloading.html


I have no problem with PHP autolading, or TS dependency injection (as seen in Angular, NestJS, TSed), or even in Scala (Play! framework via Guice). Because it's not magic (since you need to import the types you know what you are getting, for PHP there's the autoload.php generated by Composer, for TS everything is in the node_modules and in the lockfile, and for both Scala and TS the compiler checks things too. Of course in TS there are sometimes problems for non-native [ie. JS + .d.ts] libs, when the typing becomes outdated).

But in Python the amount of magic shit one had to do to get things loaded in a sane manner was always somehow a serious burden.

.. but in Ruby (Rails). Well, yeah, all you have is a lockfile, and nothing else. No imports, no typing. That gets crazy really fast. :)


You can use DI in Python too--it's an architectural pattern that can be used in any project regardless of language.

I've never seen or used autoloading in Python. What would the use case for that be? I guess it could be "convenient" to avoid imports? That seems like a bad idea to me and not very "Pythonic."

I'm not sure if this is relevant to your comment, but I've seen people abusing sys.path in Python projects instead of setting up a proper package and installing it.

Modern Python package/dependency managers like Poetry make this nicer/easier than the old school setup() approach and they also create lock files.


We had problems with circular imports back then in a Flask based backend. Also we wanted configuration based loading. Local dev, testing, prod, on premise. Plus multi-tenancy meant that everything was very-very dynamic and additional layers of configurability were present based on the request. (So basically request scoped service injection was needed. Of course we did not do that, we just passed around a bunch of parameters in a "very Pythonic way".)


Circular imports often indicate an architectural issue. In any case, there are easy solutions in the rare cases where it's absolutely necessary, like putting one of the imports at the bottom of one of the modules or in a function.

Regarding configuration based loading, that's something that's pretty common in Python webapps, and injecting capabilities/services per request seems like it would be pretty straightforward (and not anti-Pythonic either, depending on the implementation).

I just don't see how Python makes this kind of thing more or less difficult than other languages.


Circular imports also are most common when you use 'from module import *'.


> But in Python the amount of magic shit one had to do to get things loaded in a sane manner was always somehow a serious burden.

Controlling how Python imports things, seems dead easy with importlib [0] (introduced in Python 3.1). You can control the namespaces, the loader, the paths, generate code on the fly, etc.

If you wanted to, say, duplicate lockfile imports using a requirements.txt file, then that's probably a teeny tiny ten line thing or something like that.

Python is quite flexible if you want to rewrite how the language is doing something.

[0] https://docs.python.org/3/library/importlib.html


Agreed. I was sloppy in my previous comment, but what I wanted to explain is that we started with good old static imports, but needed a lot more dynamic stuff (heavily customizable deployments - onprem, dev, SaaS prod, etc)

Python can do this, but we had to accept that yep, we need a lot of elbow grease to be able to use regular libraries plus have a highly environment and request dependent "stack".


Again, your complaint, while valid, has nothing to do with autoloading, but with namespacing: https://news.ycombinator.com/item?id=29117535


Ah yes, now I remember why I stopped learning ruby and was generally disgusted with the language. Along with the standard library having numerous ways to do the same thing and the general use of monkey patching (and the ruby users actually thinking monkey patching is good)


Author here - I agree on explicitness, it can be a bit maddening to trace where a symbol comes from in Ruby. Though a good IDE helps.



> I think it’s important to say that when you’re debugging anything goes. I mean anything. Global variables, redefining methods, adding conditionals, manipulating the load path, monkey patching, printing the call stack, anything.

This is the thing that keeps me coming back to Ruby. The core philosophies of the people who influence Ruby just align so well with my own.

[Back in 2008ish I had a choice to learn Ruby or Python, after working with languages including C and Java. I started with Python, but switched to Ruby because Python launched slower and had no native Integer type (important for old ARM CPUs of the era with no FPU), but it's Ruby's philosophy and potential for poetic (but readable!) flow that make it stick now over a decade later]


Yes, I think only Clojure surpasses the level of flow you can get with Ruby. Elixir also comes close.


> I can see a name and match that to an import and match that to a package.

I like this too, but... I don't find dynamic languages to be very explicit.

Given that Python is a dynamic language, I only going to know what I can do with (or what is true about) the arguments to some function if I have worked with the codebase previously (or have sufficiently good documentation, and unit test coverage).


At least with an explicit import you have something to search for.

I have often spent several minutes trying to find a definition for a ruby method/modules/class only to finally have to run it and dump the file location manually with `source_location` to find out it's really a 3rd party gem added to the project.

Admittedly, Rails makes this much worse.


Let alone other 'magical' techniques Ruby affords, e.g. you can re-open a class to monkey-patch more methods in; or even define method_missing so that the methods an object has are dynamic. Some very neat stuff.


I agree.

I'm not the biggest Python fan myself and would not recommend it for large projects, but I use it a lot both at work and personally for scripting/small stuff. It feels dumb, but I like dumb for those use cases.


Well.... except when the package doesn't match the import path. Then you can get stuck in a merry search for "what the heck is X and why is it not in our lockfile".


The discussion of Ruby could use a mention of Enumerable.

https://ruby-doc.org/core-3.0.2/Enumerable.html


C# .NET implements quite a lot of these too via LINQ, albeit with different names (e.g. map is called Select).

https://docs.microsoft.com/en-us/dotnet/api/system.linq.enum...


Oy vey, LINQ. Meshuggeneh. Nothing like dropping COMPLETELY out of your language's idiom for a line or two to implement some Enumerable-like behavior.


It is interesting that this kind of context switch for some programmers is a breath of fresh air, and for others its a horrible hack.

I think the fact this is not an objectively good or bad thing has led to a lot of the tension we see in other aspects of programming.

I think a connected issue is tooling: if you presume a certain floor on tooling, more 'advanced' things like multiple composed domain languages become much more feasible since the tooling can remove the need for memorization and help guide construction without fear of errors. But if you presume all tooling beyond text editing should be regarded as non-essential at best and superfluous at worst, it greatly constrains the set of things in programming one can consider sane or not. I do wish we had more projects experimenting + getting traction with "high tooling, high abstractions, high domain mixing" so we could get more literacy around these methods, but I think the vast majority of programmers today have abandoned these kinds of ideas as good or worthy of further development. On pure interia alone, I long ago capitulated to the idea we will be stuck in text editors and having heated debates over typographical syntax for programming languages for the foreseeable future.

People working on things like LINQ and more advanced domain mixing like MPL or (where I once worked) Intentional Software run up against lots of resistance from people I think probably due to some fundamental differences in assumptions, but I'm not really sure what the deepest differences are.


I'd argue when I'm writing C# in a web-development setting (ASP APIs etc.) that a large majority of the code is LINQ. Especially if using something like Entity Framework. It's really just part of the language now.


A line of LINQ looks and works nothing like a line of C#, and the difference is even worse in VB. Whether it's "part of the language" or not, it's a fundamentally different way of writing logic than the base language you're writing around it. I agree it's completely coupled with EF, but I hate EF too. (After you've used ActiveRecord for over 10 years, any other ORM just feels like a ball and chain, but that's another topic.)


> A line of LINQ looks and works nothing like a line of C#

Maybe if you're using the query form, but if you're using the method form, I'd argue that they work together very nicely.

For me, at least, I've found embracing Lambda expressions to be very helpful in a number of cases, between Linq, or writing my own code for doing things like rendering tables (long story there).

Granted, I learned C# right after LINQ was released, but I'd still argue that LINQ is well worth having around.


I came from a Rails background and it was definitely painful to learn EF since it has a lot of nuances and things you wouldn't know without experience (e.g. How things mutate throughout the object lifecycle, migration funkiness, how tracking works and 'AsNoTracking()' etc.)

Think I'd still prefer ActiveRecord really but after you get used to EF (Core) it's still a good tool.


Are you using the query syntax instead of the method syntax? Because the method syntax is completely in line with normal OO code (it's just a bunch of chained methods). I'll agree that the query syntax is weird, but I haven't seen it used much.


Are you talking about the `from x in y select x.z` syntax or the `y.Select(x => x.z)` syntax? Because the latter is the one I most commonly see, and it fits in fine with the rest of the language.


Out of idiom? Extension methods, the way LINQ is implemented, has been introduced in C# 3


Author here, wow that's great! I am still learning Ruby (hence the article to try to grok things).


If one is having to implement an each or map or select method in ruby, then I can definitely appreciate the differences you point out. Even though I've been using ruby a long time, it would frankly still be a bit of a pain to have to remember the details of how yield works.

Because Enumerable provides so much functionality, I almost never need to implement methods like those. So what I really care about a lot more are the ergonomics of using each, map, select, reject, all?, any?, etc, which I use very frequently.


Yes though the point is the same, Enumerable gives the objects methods (internal iteration), in Python you would use built-ins max or filter or a list comprehension on an iterator (external iteration).


as suggestion, regarding the following code:

Stuff.new().each do |item|

  puts item
end

In idiomatic ruby, we drop this parenthesis, so the code will look like Stuff.new.each ...


puts Stuff.new.map(&:to_s).join("\n")


Enumerable is one of my favorite aspects of the Ruby standard library! I've been writing it off and on for quite some time and still discover incredibly useful and elegant functions in there. It's incredibly rare to not find what I need when working with collections.


Indeed.

The real magic is when you start doing things like:

some_enum.collect(&:+)

This simple line will do everything from sum numbers to concatenate strings, etc.


While this is cool, you can also just do `some_enum.sum` (also works with string concacts)

One thing that is cool about the .to_proc shorthand though is, for example, you have a method

def square_it(x); x * 2; end;

people tend to write an enumeration that uses it like

items.map {|i| square_it i }

but you can actually write it like this

items.map(&method(:square_it))

So not only can you write shorthands to methods on the object itself, you can write shorthands to other methods and "automagically" pass the item as its argument. Nifty stuff.


Not a rubyist, but I love that collect() made it into Rust, too. I feel like it takes the role that generators have in Python, but is much easier to reason about in terms of the control flow and what data is being passed where.


Enumerable#collect is just an eager version Python map() in method rather than function form (#map is a avaulable as an alias for #collect); it is a little more concise for when the function you are mapping over is a method on the object being processed (even more than Ruby’s normal advantage with lambdas), but not fundamentally more powerful.

(Enumerable#lazy gives you an object where collect/map is, and other merhods are, lazy rather than eager.)

> I feel like it takes the role that generators have in Python

Ruby has generators, though in either Ruby or Python map/collect (the lazy versions, in Ruby) can be used for some of their uses, since it basically produces a generator from an input collection or generator and a mapping function.


#collect is an alias for #reduce, not #map, making it like Python reduce().


> #collect is an alias for #reduce

It’s not, though. (If it was, we could write about its equivalence with Python’s [from functools in py3, core in Py2] reduce function, though, saying much the same thing as about the actual equivalence with map upthread, except reduce/inject is less often a substitute for generators than map/collect.)

#map is an alias for #collect. [0]

#reduce has the alias #inject. [1]

[0] https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-col...

[1] https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-inj...


Then should the example in the above comment, `some_enum.collect(&:+)`, be using reduce instead of collect?


Yes. My bad. :)

I haven't been a professional Rubyist in some years but my habit here was to use inject.


You’re right. My apologies.


Hmmm, I don't think this article accurately represents how people write python since ~2005. I'm biased because I use python every day, but there's a big objective mistake.

> Ruby flips the script, giving the objects deeper customizability.

Not really, because python allows you to do things the Ruby way, but I'm not sure the reverse is true.

    class Stuff:
      def __init__(self):
        self.a = [1, 2, 3, 4]

      def __iter__(self):
        for item in self.a:
          yield item
and you can write it more simply in this case.

    def Stuff():
      a = [1, 2, 3, 4]
      for item in a:
        yield item
What about the select and map example?

    class Stuff:
      def __init__(self):
        self.a = [1, 2, 3, 4]

      def __iter__(self):
        for item in self.a:
          yield item

      def map(self, f):
        for item in self.a:
          yield f(item)
Which can be used just like the ruby example, with syntax of similar ugliness.

    print(Stuff().map(lambda item: item))
I think I could come up with a python example that maps 1:1 onto pretty much any ruby example, but I don't think it's possible in general to find ruby examples that map onto more complicated python.


Author here! Very true, though I was trying to focus on idiomatic python that pushes logic into for loops, list comprehensions, etc w/ composable iterators through tools like those found in itertools (zip, etc...)

Since Python doesn't have Ruby's blocks, you have to define a non-trivial function as a free function, so it's less likely you'll do this. (Python's lambda's are far more limited than Ruby's blocks.)

You can also trick Ruby to return a new value every time a method is called for iteration, but again my focus on what I see as idiomatic in the two languages


Your Python example is far from idiomatic, though. If a Python programmer were forced to write `Stuff`, they'd write something like:

    class Stuff:
        def __init__(self):
            self.a_list = [1,2,3,4]
        def __iter__(self):
            return iter(self.a_list)
--- or do what the other person did, and make `__iter__` a generator, which isn't necessary in this case but is much more flexible.


Yes, but if you’re new to Python you’re not going to grok this.


> think I could come up with a python example that maps 1:1

My take on it:

    class Stuff:
        def __init__(self):
            self._list = [1, 2, 3, 4]
        
        @property
        def each(self):
            for el in self._list:
                yield el
    
    for item in Stuff().each:
        print(item)
It's even less verbose than the Ruby equivalent in the original article, thanks to the indentation-defined blocks.


AFAIK, there is no reason to use the form “for el in self._list: yield el”, unless you are running Python 3.2 or older.

Why not:

  each = self._list
Or, if you need to be able to re-assign self._list to a new object:

  @property
  def each(self):
    return self._list
Or, if you for some reason need it to return an iterator:

  @property
  def each(self):
    return iter(self._list)
Or, if you really want it to be a generator function:

  @property
  def each(self):
    yield from self._list


This is not idiomatic ruby, you would do

    class Stuff
      attr_reader :list

      def initialize
        @list = [1, 2, 3, 4]
      end
    end
Then:

    Stuff.new.list.each { |item| ... }
    Stuff.new.list.map { |item| ... }
    Stuff.new.list.select { |item| ... }
And if you want top-level each/map/select methods, you could do

    delegate :each, :map, :select, to: :list


Including `Enumberable` and implementing `each` gets you all those other methods for "free".

    class Stuff
      include Enumerable

      def initialize
        @list = [1, 2, 3, 4]
      end

      def each
        @list.each{ yield _1 }
      end
   end


Exactly. It is an interesting idea, but it makes me question the examples when `Enumerable` does not appear in the post.


Wouldn't it be similar to use this in the python example?

   class Stuff(list):
       pass
kinda makes the article pointless. List obviously only serves as an easy to understand example, not something you are actually trying to implement.


From an entirely tongue in cheek perspective isn't all ruby idiomatic.


It's also worth mentioning the Enumerable module that can be mixed in to classes, which gives you `map`, `select`, and much more for free by implementing `each`.

https://ruby-doc.org/core-3.0.2/Enumerable.html


I thought the examples were just for illustration about how iteration works in Ruby.


Author here, nice! TIL :)


I have written Ruby professionally for like fifteen years and I have not once witnessed a `for` loop.

I’m genuinely confused as to where you picked this up. Enumerator and Enumerable are endemic.


I would actually say that if I found a for loop in Ruby, it will be a code smell.


~15 years here too, and I can't remember my last for loop either.

I think for loops are mostly used in Ruby by people that picked it up in other languages first and simply never unlearned it.


Almost twenty years here. I have definitely seen and used `for` loops, but they are the _exception_ and usually used in one-off scripts.


At a deeper level, this comes down to internal vs. external iteration, and, more generally, how languages weave together concurrent threads of execution:

https://journal.stuffwithstuff.com/2013/01/13/iteration-insi...


Ruby is the first language I learned (apart from dabbling with Visual Basic as a kid). I still reach for it to actually get things done. One thing I always appreciated about it was the consistency of everything being an object and how that allows you to just chain methods together. Kind of reminds me of Lisp in it's consistency (although in Lisp everything is data or a list, not an object). Later I dabbled in Smalltalk (Pharo) just for fun to try to understand what inspired Ruby's object model.

I've tried Python, hated the inconsistency of it, but then again I never tried any C family language before learning Ruby. Calling Each then feeding it a block feels more natural and consistent than a for loop IMO, even if for loops are the standard 'programming' way of doing it. It does make sense to use it as an analogy to describe the differences between the languages.


> I've tried Python, hated the inconsistency of it

Can you elaborate on this? I’ve written in both Python and Ruby, but I’m not sure what you mean by this critique of python. I wouldn’t characterize either language as “inconsistent.”


In Python some standard operations are global functions and some are methods. Which are which isn't exactly clear. It's also kind of irrational and random but I hate seeing __init__ everywhere.

For example, to find the length of an array in Python you use the function len(). In Ruby you call the method .length. As the article is about, loops in Python are for blah in blah. In Ruby they're blah.each and for loops are just sugar (not sure why they're there but you can avoid them). In Ruby pretty much everything is an object and you just call methods. Very few keywords, global functions, etc...

Also, as the other poster said, if you call a[0] on an empty array, in Ruby you get nil, in Python it throws an error. Not sure which is better or considered more 'proper', but Ruby's behaviour is what I'd personally expect in a dynamic language.


> In Python some standard operations are global functions and some are methods.

Typically, all of the former are also the latter because the global function just calls the corresponding method. E.g., len(x) works by calling x.__len__().

> As the article is about, loops in Python are for blah in blah. In Ruby they're

...for blah in blah, if you choose to use them at all, which has the same semantics as the python.

It relies on method calls to #each under the hood, and usually in Ruby you won't bother with for/in, but it is there still...


a[0]. a is an array. If the array is empty, what's the result, in both languages?


You could dig even deeper. The reason for these differences in how the for loop works happen because Python comes from the procedural tradition, while Ruby comes from the Smalltalk tradition.

In other Smalltalk-inspired languages (but not Ruby), you can do the same thing with an even simpler language feature: if-statements. In procedural languages, including object-procedural languages such as Java and Python, an if-statement is a core language feature with its own syntax. In Smalltalk, conditions are kicked out of the core language and into the standard library. You have methods on the True and False objects that either take an anonymous function and execute it or perform a noop, or take two anonymous functions and decide which one to execute.


I have nothing to contribute to the content of this article, but I really want to express my gratitude to the author.

I've been using Python for over a decade and installed Ruby once or twice just to touch it, and I really like how this article has managed to bring Ruby onto my radar, not as something which I should use, but should appreciate.

For me it was just a Python alternative which some companies really do like, but this article told me a bit about the beauty of the language. Nice differences.


Thanks! I appreciate the kind words!


To me, the biggest fundamental difference between Ruby and Python seems to be that:

In Python methods are implemented via callable attributes (it's dicts all the way down)

In Ruby attributes are implemented via methods (it's messages all the way down)

I've been working professionally in Ruby for 5+ years, but non-professionally using/dabbling Python for 15 before that. Personally I still find Python more intuitive though even if I haven't used it for ages - it just fits my brain more automatically and quicker. Ruby is nice overall (ie the smalltalk inspired bits), but the more Perl inspired bits irk me, and parts of the Ruby community can produce some insane library/framework code trying to make interfaces as "elegant" as possible.


As an almost exclusive Python programmer, I’ve often looked at Ruby’s block syntax with jealous eyes. It would be so nice. But I wonder if that’s maybe the exact thing that I also like about Python: the language is pretty damn simple in its essence. You want to minimize cleverness wherever possible, and that idea is firmly planted in the community. Ruby has this sense that Ruby is almost a meta language, you create cool “DSLs” in it to accomplish your goal. That makes it harder to fit into the brain also.


For what it’s worth, the language itself is extremely simple and composable, which is what makes it possible for people to use it as a kind of metalanguage.

That was really popular in Rails gems around the time that Rails itself peaked in popularity, but has since died off considerably. It was never especially common outside of Rails gems. There aren’t that many things that call for DSLs.


DSLs remain very common in Ruby, but Rails taught us to avoid littering. DSLs now tend to be intentionally very tightly scoped.

E.g. Rouge, the Ruby equivalent to the Pygments syntax highlighter, defines DSLs to define lexers and themes. The do so in terms of methods on a meta-class so that the DSLs are confined to the definition of a lexer or theme class inheriting from the right classes, so there's a single place to look at the definitions, and their use don't leak all over the place.


I think that really comes down to how your brain works or the way you think about logic. For me Ruby is a lot easier to wrap my head around and understand than Python is.


The author overlooked the most mind-bending feature of Ruby blocks, the thing that makes them so very different than lambdas in other languages: Blocks can return from the enclosing function.

  def process
    collection.each do |element|
      return element if condition
    end
  end
This has the effect of popping three (or more) frames off the stack. From the perspective of #each, yield never returns.


This feels a bit ironic given how much the for loop has been villainized in numerical computing, by none other than languages like Python—and Matlab and R—where for loops are so awfully slow that you can only get performance if you avoid them like the plague and write (sometimes awkward) vectorized code that pushes all the for loops down into some C library (or C++ or Fortran). Compared with, say, Julia, where people are encouraged to "just write yourself a dang for loop"—because it's not only effective and simple, but also fast. I guess what I'm saying is that it feels like even though Python may embrace the for loop as syntax, that affinity seems superficial at best, since if you care at all about performance then Python immediately rejects the for loop.


In Python numeric computing it's common for your outer loops to be for loops and your inner loops to be vectorized PyTorch/whatever.

I personally like being able to easily comprehend and control what's being vectorized. Maybe it would be nice if my compiler could automatically replace any inefficient loops with vectorized equivalents, and I could think in whichever idiom came more naturally to the problem at hand. But I don't think there's anything too illogical about looping over epochs and batches, and then computing your loss function with matrices. Maybe I'm just used to a suboptimal way of doing things :)


> Maybe it would be nice if my compiler could automatically replace any inefficient loops with vectorized equivalents

The trouble is that a for loop is much more expressive than vectorized operations, so most for loops cannot be transformed into vectorized equivalents. The reason convincing people to write vectorized code works for performance is that you're constraining what they can express to a small set of operations that you already have fast code for (written in C). Instead of relying on compiler cleverness, this approach relies on human cleverness to express complex computations with that restricted set of vectorized primitives. Which is why it can feel like such a puzzle to write vectorized code that does what you want—because it is! So even if a compiler could spot some simple patterns and vectorize them for you, it would be incredibly brittle in the sense that as soon as you change the code just a little, it would immediately fall off a massive performance cliff.

I guess that's actually the second problem—the first problem is that there isn't any compiler in CPython to do this for you.


With hindsight, the languages turned out to be a bit "too dynamic" for their own good. Very few are changing variable types often enough for that feature to be useful. The downside, makes typing bugs possible/more likely, and slows down every access. Par for the course, would say that slow loops are a symptom not a cause.


matlab has been jit compiled for years now, the "for loops are slow" dogma is over.

numerical computing in python is kinda weird as it wasn't the original purpose and the fast math libraries were bolted on as an afterthought, but even then tools like numba do the same in python, although there's a bunch of nuance in writing simple enough python and hinting at the correct types for the variables in order to get it to compile something reasonable.

julia's let's use strict types, jit compile everything from day one and avoid locking approach is nice though.


After numba’ing several nontrivial pieces of numpy code in my life, you might as well just rewrite it in nopython mode for Cython unless it’s very trivial stuff. Numba errors and partial coverage of numpy is a huge time sink in my experience.


it gets even more fun when you need to support both mkl and standard numpy libraries, as well as several bit widths.

but it can be done and goes to show that it's not going to be putting optimization experts out of work yet.

reading about the state of autograd libraries in julia seems to indicate the same.


Interestingly, Java has both styles. You can do

    for (var i : list) {...}
or

    list.forEach(i -> ...);
Except for cases with local variables, I have absolutely no idea which I should prefer. Kotlin makes a cute case for the functional style with it's lambda shorthand:

    list.forEach {
        ...
    }
I've found the functional style helpful for times I want almost add a language feature to avoid boilerplate. The functional style was added without special-purpose language changes, but the iterator version required Java to add Collections, Iterators, and eventually fancy for-each syntactic sugar.


The big advantage of the functional style in Java is that you are disallowed at the compiler level from modifying the contents of the collection you're iterating over. That sort of compiler-enforced good practice is definitely for the best.


> you are disallowed at the compiler level from modifying the contents of the collection you're iterating over

This fails at runtime, not compile time for me:

    x.forEach(i -> x.add("a"));


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

Search: