Bruce
Bruce

Reputation: 1678

Ruby: the yield inside of a block

Here is an example of scan:

"abcdeabcabc".scan("a")

So it returns an array of 3 a's. Another example of scan:

"abcdeabcabc".scan("a") {|x| puts x}

which just output each "a", but still output an array, and this time it is actually the original string that it returns.

So from the documentation and the behavior above, the scan either returns an array (no block is given), or returns the original string before which some side-effects take place. The point is that both cases returns something.

Then what will happen if I put a "yield" inside of the block? What will be returned? Or, none? What will be the type of the return value?

"abcdeabcabc".scan("a") {|x| yield x}

The above will not work as Ruby complains that no block is given. Which makes some sense to me. But if it is part of a class method, say, self-implemented "each", then the following works:

class Test
  def my_each
    "abcdeabcabc".scan("a") {|x| yield x}
  end
end
# => :my_each

t = Test.new
# => #<Test:0x007ff00a8d79b0>

t.my_each {|x| puts "so this is #{x}"}
# it works. Outpus 3 a's then return the original string.

So, what is the return value of the my_each method of Test class? Is that a list of yield's or something? But as discussed before "abcdeabcabc".scan("a") {|x| yield x} segment will be complained by Ruby until a block is given. What happened internally to give the block of my_each to the segment inside of my_each implementation?

Upvotes: 5

Views: 4874

Answers (3)

D-side
D-side

Reputation: 9485

The block is passed similarly to the argument of that function. This can be specified explicitly, like so:

class Test
  def my_each(&block)
    "abcdeabcabc".scan("a") do |x|
      puts "!!! block"
      yield x
      # Could be replaced with: block.call(x)
    end
  end
end

Technically, it's exactly the same (puts put in there for clarification), its presence is not checked the way it is usually done for arguments. Should you forget to give it a block, the function will halt on the first yield it has to execute with exactly the same LocalJumpError (at least, that's what I get on Rubinius). However, notice the "!!! block" in the console before it happens.

It works like that for a reason. You could check whether your function is given a block, if it is specified explicitly as above, using if block, and then skip the yields. A good example of that is a content_tag helper for Rails. Calls of this helper can be block-nested. A simplistic example:

content_tag :div do
  content_tag :div
end

...to produce output like:

<div>
  <div></div>
</div>

So, the block is executed "on top" (in terms of call stack) of your method. It is called each time a yield happens as some sort of function call on a block. It's not accumulated anywhere to execute the block afterwards.

UPD:

The Enumerator returned by many eaches is explicitly constructed by many iterators to save context of what should happen.

It could be implemented like this on my_each:

class Test
  def my_each(&block)
    if block
      "abcdeabcabc".scan("a") { |x| yield x }
    else
      Enumerator.new(self, :my_each)
    end
  end
end

Upvotes: 4

Arup Rakshit
Arup Rakshit

Reputation: 118261

"abcdeabcabc".scan("a") {|x| yield x}

In the above case, #scan is passing the each matched character to the block associated to it.

Now inside the block of #scan, you are calling yield, which actually then calling the block you passed to the method my_each. Value of x is passed to the block you passed with the method my_each call.

It is too simple, no confusions.

what is the return value of the my_each method of Test class?

As per your current code, the return value should be the #scan method return value, which in turn causes the result of the last statement of the block associated with the method #my_each (if called) or the receiver on which you called the method #scan.

Is that a list of yield's or something?

Yes, yield will be called, as many matches will be found by the #scan method.

Consider the below example :-

"abcdeabcabc".scan("w") {|x| yield x}

Here the block associated with the method #scan will not be called, as #scan didn't find any match, that's why yield wouldn't be called also and as a result method #my_each wouldn't output block expression(passed with the method) result, but "abcdeabcabc".

Upvotes: 1

sawa
sawa

Reputation: 168081

Since a block is given to scan, the original string is returned. It does not matter what is done inside the block.

Upvotes: 1

Related Questions