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)

Upvotes: 1

Views: 2628

Answers (3)

manzoid
manzoid

Reputation: 1225

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.

Upvotes: 7

ireddick
ireddick

Reputation: 8408

yield simply passes (yields) control to a block of code that has been passed in as part of the method call. The values after the yield keyword are passed into the block as arguments. Once the block has finished execution it passes back control.

So, in your example you could call #each like this:

CartProd.new([1, 2], [3, 4]).each do |pair|
  # control is yielded to this block
  p pair
  # control is returned at end of block
end

This would output each pair of values.

Upvotes: 0

Anton
Anton

Reputation: 3036

What exactly do you not understand here? You've made an iterator that yields all possible pairs of elements. If you pass CartProd#each a block, it will be executed a.length*b.length times. It's like having two different for cycles folded one into another in any other programming language.

Upvotes: 0

Related Questions