理解从Ruby中的procs返回

时间:2015-08-13 07:32:14

标签: ruby metaprogramming internals

我想知道如何将一个块传递给一个方法,该方法会在return上创建方法yield

天真的方法不起作用:

def run(&block)
  block.call
end

run { return :foo } # => LocalJumpError

包装在另一个过程中具有相同的效果:

def run(&block)
  proc { block.call }.call
end

run { return :bar } # => LocalJumpError

所以我认为return语句绑定到当前receiver的{​​{1}}。但是,使用binding进行尝试证明我错了:

instance_eval

所以问题是:

  1. 怎么办呢?
  2. 返回上下文如何与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 语句绑定。这个连接是否可以通过语言的API访问?
  3. 这是故意以这种方式实施的吗?如果是 - 为什么?如果不是 - 解决它有什么障碍?

2 个答案:

答案 0 :(得分:2)

  

我认为return语句绑定到当前receiver的{​​{1}}。

只有方法才有接收器。 binding不是一种方法:

return

尝试将其作为方法调用不起作用:

defined? return #=> "expression"
  

尝试使用def foo send(:return, 123) end foo #=> undefined method `return' 证明我错了

虽然instance_eval评估接收器上下文中的块(因此您可以访问接收器实例方法和实例变量):

instance_eval

...它评估当前绑定中的块(因此您无权访问任何局部变量):

class MyClass
   def foo(&block)
     @var = 123
     instance_eval(&block)
   end
end

MyClass.new.foo { instance_variables }
#=> [:@var]
  

如何做到这一点?

您可以使用class MyClass def foo(&block) var = 123 instance_eval(&block) end end MyClass.new.foo { local_variables } #=> [] ,但这需要一个字符串:

eval

或者通过将绑定传递给块(再次使用def foo var = 123 eval yield nil end foo { "return var * 2" } #=> 246 ):

eval

答案 1 :(得分:1)

块中的

return在定义块时从封闭方法返回(即,创建块的闭包)。在您的示例中,没有要返回的封闭块,因此您的例外。

这很容易证明:

def foo(&block)
  puts yield
  puts "we won't get here"
end

def bar
  foo { return "hi from the block"; puts "we never get here" }
  puts "we never get here either"
end

puts bar # => "hi from the block" (only printed once; the puts in `foo` is not executed)

在proc中返回会立即返回proc,而不是在proc下的堆栈上的方法:

def foo(&block)
  puts yield
  puts "we will get here"
end

def bar
  foo &->{ return "hi from the proc"; puts "we never get here" }
  puts "we will get here too"
end

puts bar
# hi from the proc      # puts from foo
# we will get here      # puts from foo
# we will get here too  # puts from bar

由于这些行为,无法实现所需的行为,其中给定块中的return将在调用块的方法中执行return,除非block是在该范围内定义的,因为这样做会要求其中一个现有行为不起作用。

你可以用throw ... catch来实现这样的东西,这有点像从任意深度压缩堆栈的方式,但你不能用它返回任意值:

def foo(&block)
  yield
  puts "we won't get here"
end

catch(:escape) do
  foo &->{ throw :escape }
end