Executing Blocks with the yield Keyword

When you see yield anywhere in a Ruby block, you should think “execute the block.” Try out the following in irb:

 >>​ ​def​ do_it
 >>​ ​yield
 >>​ ​end
 =>​ ​:do_it

This is a pretty plain-looking piece of code that just executes any block you give it:

 >>​ do_it { puts ​"I'm doing it"​ }
 I'm doing it
 =>​ ​nil

Outputting a string doesn’t return any value. In other words, this block is executed merely for its side effects. Now, let’s make a block that returns a value and pass it into the do_it method:

 >>​ do_it { [1,2,3] << 4 }
 =>​ [1, 2, 3, 4]

What happens when we don’t pass in a block to do_it?

 >>​ do_it
 LocalJumpError: no block given (yield)
  from (irb):28:in `do_it'

irb helpfully informs you that the method was not given a block to execute. You might want to pass arguments into a block. For example, say you want a method that passes in two arguments to a block:

 >>​ ​def​ do_it(x, y)
 >>​ ​yield​(x, y)
 >>​ ​end
 =>​ ​:do_it

Now, we can pass in and execute any block that takes two arguments:

 >>​ do_it(2, 3) { |x, y| x + y }
 =>​ 5
 
 >>​ do_it(​"Ohai"​, ​"Benevolent Dictator"​) ​do​ |greeting, title|
  "#{greeting}, #{title}!!!"
  end
 =>​ ​"Ohai, Benevolent Dictator!!!"

There’s a tiny gotcha to yield’s argument-passing behavior. It is more tolerant of missing and extra arguments than you might expect. Missing arguments will be set to nil, and extra arguments will be silently discarded.

Let’s modify the method and give it fewer arguments. In this definition of do_it, yield is only given a single argument:

 >>​ ​def​ do_it(x)
 >>​ ​yield​ x
 >>​ ​end
 =>​ ​:do_it

Observe what happens when the method receives a block that expects two arguments:

 >>​ do_it(42) { |num, line| ​"​​#{​num​}​​: ​​#{​line​}​​"​ }
 =>​ ​"42: "

If you find this behavior slightly strange, you can think of yield acting a little like a parallel assignment, in that nils are assigned to missing arguments:

 >>​ a, b = 1 ​# => 1
 >>​ b ​# => nil

As previously noted, missing arguments are assigned nil, which explains the lack of an error. What happens if the yield is given more arguments than expected? Once again, think about the parallel assignment analogy:

 >>​ a, b = 1,2,3
 =>​ [1, 2, 3]
 >>​ a
 =>​ 1
 >>​ b
 =>​ 2

In this case, 3 is discarded. Redefine do_it once more:

 >>​ ​def​ do_it
 >>​ ​yield​ ​"this"​, ​"is"​, ​"ignored!"
 >>​ ​end
 =>​ ​:do_it

Now, pass in a block that takes in no arguments:

 >>​ do_it { puts ​"Ohai!"​ }
 =>​ Ohai!

Once again, Ruby executes the code without a hitch. This is also consistent with the parallel assignment behavior as previously demonstrated. Keep in mind this argument passing behavior; otherwise, you might waste precious time figuring out why Ruby doesn’t throw an exception when you think it would, especially when writing unit tests.

Now let’s look at the relationship between blocks and closures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.214.27