Search code examples
pythonrubylanguage-comparisons

Porting from Ruby to Python: What to do with 'yield'


I currently try to port a piece of code from Ruby to Python to do a bit algorithm research. I have no experience in Ruby and don't know how to handle the 'yield' keyword.

The code is for a Myers diff algorithm and fully described in this blog

This is the code snippet I don't understand:

  while x > prev_x and y > prev_y
    yield x - 1, y - 1, x, y
    x, y = x - 1, y - 1
  end

Is there a way to approximate this in Python?


Solution

  • Almost identically. Though the semantics of Python's and Ruby's yield is somewhat different, in this case they coincide almost exactly.

    Ruby's yield invokes a block that is passed into the function, giving it its parameters.

    Python's yield makes the function a generator, and generates one output from it.


    Both of them only make sense in context of a function, so just your while loop is too short a context to use it. But let's take something like it as a simplified example, in Ruby:

    def numbers_and_doubles(n)
      i = 0
      while i < n
        yield i, 2 * i
        i += 1
      end
    end
    

    This function accepts a block with one parameter, then generates numbers up to that number along with their double and executes that block with those parameters:

    numbers_and_doubles(5) do |num, double|
      puts "#{num} * 2 = #{double}"
    end
    

    As blocks are basically the same thing as callback functions, it is equivalent to this Python:

    def numbers_and_doubles(n, callback):
        i = 0
        while i < n:
            callback(i, i*2)
            i += 1
    
    def output(num, double):
        print(f"{num} * 2 = {double}")
    
    numbers_and_doubles(5, output)
    

    On the other hand, Python's yield creates a generator - a function that returns a function that can produce values on demand:

    def numbers_and_doubles(n):
        i = 0
        while i < n:
            yield i, 2 * i
            i += 1
    

    The most natural way to consume a generator is via a for loop:

    for num, double in numbers_and_doubles(5):
        print(f"{num} * 2 = {double}")
    

    In Ruby, the closest literal translation is Enumerator:

    def numbers_and_doubles(n)
      Enumerator.new do |yielder|
        i = 0
        while i < n
          yielder.yield(i, i*2)
          i += 1
        end
      end
    end
    

    and the most natural way to consume an Enumerator is using each (which is what Rubyists prefer over for):

    numbers_and_doubles(5).each do |num, double|
      puts "#{num} * 2 = #{double}"
    end
    

    But, as I said, even though they do something slightly different, the original Ruby above (with yield) is surprisingly similar to the original Python above (with yield). The way they are consumed is slightly different, but appropriate to each language's idiom.

    In your case, if you leave yield exactly as it is in your Python, the line that consumes it changes from Ruby's

    backtrack do |prev_x, prev_y, x, y|
    

    to Python's

    for prev_x, prev_y, x, y in backtrack():
    

    You can read more at Python yield vs Ruby yield.


    Note that the most natural way to write this loop is not while in either language (I'd use range in Python and times in Ruby), but I wanted to have code that looks similar for both languages, for comparison.