32

Is it possible to make yield keyword work inside a block given to define_method? Simple example:

class Test
  define_method :test do |&b|
    puts b    # => #<Proc:...>
    yield
  end
end

Test.new.test {
  puts "Hi!"
}

This code produces following error in both Ruby 1.8.7 and 1.9.0:

test.rb:4:in `test': no block given (LocalJumpError) from test.rb:8

The strange thing is the b block variable != nil but block_given? returns false. Is it intentional Ruby behaviour not to recognize blocks by Proc objects?

Edit: Regards to Beerlington's answer: b.call() is is not what I am looking for. Block variable was used only to indicate that block is actually given, and is not detected inside define_method.

Reason why I need use yield instead of block.call

I am willing to write some extension to the way how new classes are defined in Ruby, thus any code You can write in pure Ruby should be accepted when I use my extension.

So similar semantics cannot be taken into consideration, because this forces users of my library to use only one proper way to pass a block. This breaks the TIMTOWTDI rule, and does not make my library transparent.

Real life example

Code below can be simplified to code above since my_def uses define_method:

require 'my_library'

class Test
  # client can write 'my_def' instead of 'def' since
  # my_library extends Class class
  my_def :test, "some parameter" do
    yield        # oh no, error :(
  end
end

Test.new.test {
  puts "Hi!"
}
Dawid
  • 4,042
  • 2
  • 27
  • 30
  • Not answering, but related: you can't test `block_given` either inside `define_method`. Test for the block, like here: https://www.ruby-forum.com/topic/3486000 – ribamar Oct 16 '16 at 11:10

2 Answers2

29

I think this is what you're looking for:

class Test
  define_method :test do |&b|
    b.call
  end
end

Test.new.test {
  puts "Hi!"
}

More at http://coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/

Peter Brown
  • 50,956
  • 18
  • 113
  • 146
  • 3
    Unfortunately this is not what I am looking for. I know I can call block this way, but I need to use yield. b variable was used only to indicate that block is actually given. – Dawid Feb 21 '10 at 18:56
  • 1
    @Dejw, This *is* the droid you're looking for. Taking &block as an argument and invoking block.call on it has the same semantics as calling "yield." Beerlington's code works in both 1.8.7 and 1.9. – Wayne Conrad Feb 22 '10 at 04:38
  • @Wayne: You may think this is the solution but is not. I did not write the purpose of using `yield` instead of `block.call` and the reason _why_ is in my question now. – Dawid Feb 22 '10 at 14:09
  • 1
    @Dejw, Thanks for the added explanation. I have to admit that I'm still confused: block.call not only has the same semantics as yield, it has the exact same syntax for the caller (see Beerlington's code: He's not passing the block in as an argument). The caller can't tell which one the method is using. What am I missing? – Wayne Conrad Feb 22 '10 at 16:41
  • 3
    The problem is that _the caller_ may write `yield` instead of `block.call`. The code I have given is possible caller's code. Extended method definition in my library can be simplified to my code above. Client provides block passed to define_method (body of a method), so he/she can write there anything. Especially `yield`. I can write in documentation that `yield` simply does not work, but I am trying to avoid that, and make my library 100% compatible with Ruby (alow to use any language syntax, not only a subset). – Dawid Feb 22 '10 at 20:27
  • 1
    @Dejw, Thanks again for your patience in explaining this. – Wayne Conrad Feb 24 '10 at 04:41
23

You cannot use yield inside a define_method block. This is because blocks are captured by closures, observe:

def hello
  define_singleton_method(:bye) { yield }
end

hello { puts "hello!" }

bye {  puts "bye!" } #=> "hello!"

I don't think your users will mind not being able to use 'yield' in the way you state ---- the syntax is nothing like ordinary Ruby method definition syntax so there is unlikely to be any confusion.

More information on why you cannot pass blocks implicitly to methods found here: http://banisterfiend.wordpress.com/2010/11/06/behavior-of-yield-in-define_method/

horseyguy
  • 29,455
  • 20
  • 103
  • 145
  • 2
    Wow, thanks for the explanation, I was wondering why doesn't it work. This should be the accepted answer – Janko Sep 26 '14 at 14:30