理解从Ruby中的procs返回

ndn*_*kov 5 ruby metaprogramming internals

我想知道如何将块传递给一个方法,该方法将return启用该方法yield.

天真的方法不起作用:

def run(&block)
  block.call
end

run { return :foo } # => LocalJumpError
Run Code Online (Sandbox Code Playgroud)

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

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

run { return :bar } # => LocalJumpError
Run Code Online (Sandbox Code Playgroud)

所以我认为该return声明receiver与当前的声明有关binding.然而,试一试instance_eval证明我错了:

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
Run Code Online (Sandbox Code Playgroud)

所以问题是:

  1. 如何才能做到这一点?
  2. 返回上下文如何与return语句相关联.这个连接是否可以通过语言的API访问?
  3. 这是故意以这种方式实施的吗?如果是 - 为什么?如果不是 - 修复它的障碍是什么?

Chr*_*ald 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)
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

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

您可以使用 throw...catch 实现类似的功能,这作为一种从任意深度压缩堆栈的方法有点有用,但您不能用它返回任意值:

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

catch(:escape) do
  foo &->{ throw :escape }
end
Run Code Online (Sandbox Code Playgroud)