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.