I was wondering how to pass a block to a method which will make the method return
on yield
.
The naive aproach doesn't work:
def run(&block)
block.call
end
run { return :foo } # => LocalJumpError
Wrapping in another proc has the same effect:
def run(&block)
proc { block.call }.call
end
run { return :bar } # => LocalJumpError
So I thought that the return
statement is bound to the receiver
of the current binding
. However, trying it out with instance_eval
proved me wrong:
class ProcTest
def run(&block)
puts "run: #{[binding.local_variables, binding.receiver]}"
instance_eval(&block)
end
end
pt = ProcTest.new
binding_inspector = proc { puts "proc: #{[binding.local_variables, binding.receiver]}" }
puts "main: #{[binding.local_variables, binding.receiver]}"
# => main: [[:pt, :binding_inspector], main]
binding_inspector.call
# => proc: [[:pt, :binding_inspector], main]
pt.run(&binding_inspector)
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => proc: [[:pt, :binding_inspector], #<ProcTest:0x007f4987b06508>]
pt.run { return :baz }
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => LocalJumpError
So the questions are:
return
statement. Is this connection accessible via the language's API?I thought that the
return
statement is bound to thereceiver
of the currentbinding
.
Only methods have an receiver. return
is not a method:
defined? return #=> "expression"
Trying to invoke it as a method doesn't work:
def foo
send(:return, 123)
end
foo #=> undefined method `return'
trying it out with
instance_eval
proved me wrong
Though instance_eval
evaluates the block in the context of the receiver (so you have access to the receivers instance methods and instance variables):
class MyClass
def foo(&block)
@var = 123
instance_eval(&block)
end
end
MyClass.new.foo { instance_variables }
#=> [:@var]
... it does not evaluate the block in the current binding (so you don't have access to any local variables):
class MyClass
def foo(&block)
var = 123
instance_eval(&block)
end
end
MyClass.new.foo { local_variables }
#=> []
How can this be done?
You could use eval
, but that requires a string:
def foo
var = 123
eval yield
nil
end
foo { "return var * 2" }
#=> 246
Or by passing the binding to the block (again using eval
):
def foo
var = 123
yield binding
nil
end
foo { |b| b.eval "return var * 2" }
#=> 246
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With