1. 23
  1. 15

    Bob Nystrom’s treatment of the same topic provides examples of the use cases bolstered by each approach. IMO the distinction goes much deeper and related to how much of the structure of the program should be determined by the program itself. Ruby’s internal iteration is an example of the fundamental principle that the iteration of the object isn’t known until it happens. Sure, maybe we could know from reading the code, but the program itself could not know (without ridiculous level of effort akin to interpreting itself). This is a big reason python code is more amenable static analysis but also a big reason ruby tests can be written much more succinctly in most cases.

    1. 8

      Python’s approach may require fewer concepts / abstractions and may be one of the reasons of it’s success, even if lately with the latest releases they seem be quickly increase in number (I’m thinking about async/await, typing, walrus and pattern matching). The real enpowerer of Ruby mental model seems to be the code block, which is something at same time more powerful and expressive than Python’s lambdas.

      The Python’s for enabled class is complete fiction:

      class Stuff:
          def __init__(self):
              self.a_list = [1,2,3,4]
              self.position = 0
          def __next__(self):
              try:
                  value = self.a_list[self.position]
                  self.position += 1
                  return value
              except IndexError:
                  self.position = 0
                  raise StopIteration
          def __iter__(self):
              return self
      

      A shared position? What’s for? You would instead code or see something like:

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

      which is way more similar to the Ruby’s one. Or:

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

        I wouldn’t say the number of required elements really increased that significantly: async/await - it’s optional and trivial if you were doing twisted/green before; typing - completely optional; walrus and pattern matching - those are new elements, but only the patterns go beyond syntax sugar. Python continues to be fairly conservative with regards to actually required changes.

        But yeah, the python iteration from the post is super contrived and basically wrong.

        1. 1

          Shared position is not correct but you’re kinda missing the point. They could’ve omitted the body entirely, what matters is the interface: def __iter__(self) does not take a code block. It is a generator producing values for the outer code that’s driving the outer loop.

          1. 1

            I’ve not missed that, I was just stating that where the Python source may seem more complex, no one would code an iteration enabled class like that.

          2. 1

            Python’s approach may require fewer concepts / abstractions

            Why do you say that? From working in Ruby some time back, and working with Python now, it seems to me that the amount of abstractions ruby uses at the language level seems to be much smaller than Python. (e.g. No with, no separate concept of iterators, no comprehensions etc. – everything is blocks).

            1. 1

              The Python’s for enabled class is complete fiction:

              Author here, thanks, I kinda knew this when I wrote it and wanted to avoid teaching the user half-a-dozen contepts to get to the more concise version. In the end, nobody would iterate a list of consecutive numbers this way anyway, they’d use range :)

              There’s a similar issue in Ruby, you would use Enumerable mixin, not define a select, map etc. But the effect and point is the same.

              But perhaps I should put a note to these better practices so nobody is copy-pasting code

            2. 4

              The first time I used Python (coming from Ruby), I noticed that decorators were a completely redundant construct in Ruby; they could just be implemented with blocks. Same with “context managers” (i.e. “with” blocks): another ad-hoc Python construct subsumed by Ruby blocks. I think just these two examples demonstrate the power of Ruby’s block construct relative to anything in Python (let alone its crippled lambdas).

              Also, this article should have formulated the issue as internal vs. external iterators and described their respective pros and cons. Neither is clearly superior in all contexts.