1

Why am I getting an error:

ERROR:

LocalJumpError
# ~> no block given (yield)

CODE:

module M
  def hello(text = 'bba')
    puts "yo-#{text}"      # => nil
  end                      # => :hello

  instance_methods  # => [:hello]

  m = instance_method(:hello)  # => #<UnboundMethod: M#hello>

  define_method(:bye) do |*args, &block|  
    yield                                 # ~> LocalJumpError: no block given (yield)
    m.bind(self).(*args, &block)
  end                                     # => :bye

end  # => :bye

class A
  include M  # => A
end          # => A


A.new.hello('vv')       # => nil
A.new.bye('zz') do |p|  # => #<A:0x00007fa8c401e090>
  puts "ggg"
end

# >> yo-vv

# ~> LocalJumpError
# ~> no block given (yield)
user2012677
  • 5,465
  • 6
  • 51
  • 113

1 Answers1

4

It's the difference in the semantics of def and define_method. See this:

module M
  def outer(&block)
    puts "outer: #{yield}"

    def inner1
      puts "inner1: #{yield}"
    end

    M.define_method(:inner2) do
      puts "inner2: #{yield}"
    end

    M.define_method(:inner3) do |&block|
      puts "inner3: #{block.call}"
    end

    inner1 { 1 }
    inner2 { 2 }
    inner3 { 3 }
  end
end

class A
  include M
end

A.new.outer { 0 }
# => outer: 0
#    inner1: 1
#    inner2: 0 (!!!)
#    inner3: 3

yield only works inside def.

Thus, inner1 calls its own block; but inner2 uses the block of the def it is in. The correct way to invoke the block inside define_method is to capture it in the parameter list (as you did), and then use #call or #[] on it, like inner3 demonstrates.

In your code, there is no def around, thus no block is available when you yield. You can use the above method, and replace yield with block.call.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I'm wondering if this is a more general feature of passing a block to another block. If you pass a block as an argument to a block, do you have to pass it as a `Proc`? – BobRodes Nov 08 '19 at 05:29
  • @BobRodes You can't call a block. You can call a method, or a `Proc`. You can only pass a block in a method call, since a method call explicitly supports a block syntax: `foo { ... }`. You cannot pass a block in a `Proc` call, because a `Proc` call does not have a special syntax, but uses a method call to do it — and `#call` and `#[]` do not accept a block. – Amadan Nov 08 '19 at 05:35
  • Right, I realize you can't call a block, and I saw the reason why the OP was getting the error was that, because of the semantics of `define_method`, he was trying to pass a block to a block instead of a method definition. So, I'm asking whether if you have `block1`, and want to pass it to `block2`, the way to do it is wrap `block1` in a `Proc` object first (by using `&whatever` in `block2`'s parameter list) and then `call` it from `block2` rather than using `yield`. In other words, is this a more general principle of which the OP's code is an example? – BobRodes Nov 08 '19 at 05:46
  • @BobRodes Again, I don't quite understand what you are asking, since "block" is not something you can have in a variable. If `block1` and `block2` are both `Proc`, then `block1[block2]` (or `block1.call(block2)`) works if `block2` accepts `|block|, and `block1[&block2]` works if `block2` accepts `|&block|`. However, this works: `class A; def m; puts "[#{yield}]"; end end; A.new.method(:m).to_proc.call { 4 }`. So you can call a `Proc` with a block (and have it `yield`) — as long as it was originally defined with `def`. – Amadan Nov 08 '19 at 05:56
  • why does yield work in this example? https://stackoverflow.com/questions/5513558/executing-code-for-every-method-call-in-a-ruby-module – user2012677 Nov 08 '19 at 14:08
  • Because `yield` is executing the block passed to `before`, which is the surrounding `def`. – Amadan Nov 08 '19 at 14:10