Archive for November 6th, 2010

November 6, 2010

Behavior of yield in define_method

At first blush it seems possible to pass blocks implicitly to methods created with define_method by simply using a yield inside the define_method block.

Take a look at the following code:

def make_method
  define_singleton_method(:hello) { yield }
end

make_method
hello { puts "hi" } #=> LocalJumpError: no block given

So, notwithstanding that we’re actually passing a block to hello the yield is failing to invoke it.

Now let’s try something else:

make_method { puts "make_method block" }
hello { puts "hi" } #=> "make_method block"

That’s right, the yield in the define_method block is actually causing the hello method to close over the block passed to make_method. The block passed to hello itself is simply discarded.

Note that Proc.new inside a define_method also uses the block passed to make_method:

def make_method
  define_singleton_method(:hello) { block = Proc.new; block.call }
end

make_method { puts "make_method block" }
hello  #=> "make_method block"

Conclusion

Everyone knows that Ruby’s blocks close over local variables and constants but it appears they close over blocks too. The behavior of yield in define_method is not therefore (in my opinion) an inconsistency as has been argued elsewhere, but it still may be confusing.

As a result of the fact blocks are captured by closures they cannot be passed implicitly to methods created with define_method; they must instead be passed explicitly using the define_method(meth) { |args, &block| ... } syntax.

Follow

Get every new post delivered to your Inbox.