33

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!"
}
1
  • Not answering, but related: you can't test block_given either inside define_method. Test for the block, like here: ruby-forum.com/topic/3486000 Commented Oct 16, 2016 at 11:10

2 Answers 2

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/

Sign up to request clarification or add additional context in comments.

6 Comments

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.
@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.
@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?
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).
@Dejw, Thanks again for your patience in explaining this.
|
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/

1 Comment

Wow, thanks for the explanation, I was wondering why doesn't it work. This should be the accepted answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.