Search code examples
rubyblockyieldenumerable

Ruby yield example explanation?


I'm doing a SaaS course with Ruby. On an exercise, I'm asked to calculate the cartesian product of two sequences by using iterators, blocks and yield.

I ended up with this, by pure guess-and-error, and it seems to work. But I'm not sure about how. I seem to understand the basic blocks and yield usage, but this? Not at all.

class CartProd
  include Enumerable
  def initialize(a,b)
        @a = a
        @b = b
  end
  def each
        @a.each{|ae|
                @b.each{|be|
                        yield [ae,be]
                }
        }
  end
end

Some explanation for a noob like me, please?

(PS: I changed the required class name to CartProd so people doing the course can't find the response by googling it so easily)


Solution

  • Let's build this up step-by-step. We will simplify things a bit by taking it out of the class context.

    For this example it is intuitive to think of an iterator as being a more-powerful replacement for a traditional for-loop.

    So first here's a for-loop version:

    seq1 = (0..2)
    seq2 = (0..2)
    for x in seq1
      for y in seq2
        p [x,y] # shorthand for puts [x, y].inspect
      end
    end  
    

    Now let's replace that with more Ruby-idiomatic iterator style, explicitly supplying blocks to be executed (i.e., the do...end blocks):

    seq1.each do |x|
      seq2.each do |y|
        p [x,y]
      end
    end
    

    So far, so good, you've printed out your cartesian product. Now your assignment asks you to use yield as well. The point of yield is to "yield execution", i.e., pass control to another block of code temporarily (optionally passing one or more arguments).

    So, although it's not really necessary for this toy example, instead of directly printing the value like above, you can yield the value, and let the caller supply a block that accepts that value and prints it instead.

    That could look like this:

     def prod(seq1, seq2)
        seq1.each do |x|
          seq2.each do |y|
            yield [x,y]
          end
        end
      end
    

    Callable like this:

    prod (1..2), (1..2) do |prod| p prod end
    

    The yield supplies the product for each run of the inner loop, and the yielded value is printed by the block supplied by the caller.