【问题标题】:Why does explicit return make a difference in a Proc?为什么显式返回会对 Proc 产生影响?
【发布时间】:2010-11-28 23:24:57
【问题描述】:
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo" 
end

def bar
  b = Proc.new { "return from bar from inside proc" }
  b.call # control leaves bar here
  return "return from bar" 
end

puts foo # prints "return from foo from inside proc" 
puts bar # prints "return from bar" 

我认为 return 关键字在 Ruby 中是可选的,并且无论您是否请求,您始终是 returning。鉴于此,我发现 foobar 具有不同的输出,这是由 fooProc f 中包含显式 return 这一事实决定的,这让我感到惊讶。

有人知道为什么会这样吗?

【问题讨论】:

    标签: ruby return proc-object


    【解决方案1】:

    Ruby 具有三个构造:

    1. block 不是对象,由{ ... }do ... end 创建。
    2. proc 是由Proc.newproc 创建的Proc 对象。
    3. lambda 是由lambda(或Ruby 1.8 中的proc)创建的Proc

    Ruby 有三个从某事物返回的关键字:

    1. return 终止它所在的方法或 lambda。
    2. next 终止它所在的块、proc 或 lambda。
    3. break 终止产生块或调用它所在的 proc 或 lambda 的方法。

    在 lambdas 中,return 的行为类似于 next,无论出于何种原因。 nextbreak 之所以如此命名,是因为它们最常与 each 之类的方法一起使用,其中终止块将导致迭代以集合的 next 元素继续, 并且终止 each 将使您中断退出循环。


    如果您在foo 的定义中使用return,您将从foo 返回,即使它位于块或过程中。要从块返回,您可以改用 next 关键字。
    def foo
      f = Proc.new { next "return from foo from inside proc" }
      f.call # control leaves foo here
      return "return from foo" 
    end
    puts foo # prints "return from foo"
    

    【讨论】:

    • 编辑也提到了 lambdas 的行为
    • 注意,在你的例子中,如果 next 被省略,行为仍然存在。
    • 顺便说一句,我认为我们都很好地回答了一个不同的问题 :) 至于为什么,我认为只有 Matz 知道,很多关于闭包的东西都违反了最小意外原则。跨度>
    • 感谢您在这里的彻底回答。真的很清楚。
    • 感谢您用很好的例子进行解释
    【解决方案2】:

    这是Procs 的语义;它不一定是所有块的语义。我同意这有点令人困惑。它的存在是为了增加灵活性(可能部分原因是 Ruby 除了实现之外没有规范)。

    行为在Proc 实现中定义。 Lambdas 的行为不同,因此如果您希望 returns 不退出 退出封闭方法,请使用 lambdas。或者,从 Proc 中省略 return 关键字。

    深入研究 Rubys 闭包is here。这是一个很棒的曝光。

    所以:

    def foo   
      f = Proc.new {
        p2 = Proc.new { return "inner proc"};
        p2.call
        return "proc"
      }
      f.call
      return "foo"
    end
    
    def foo2
      result = Proc.new{"proc"}.call
      "foo2 (proc result is: #{result})"
    end
    
    def bar
      l = lambda { return "lambda" }
      result = l.call
      return "bar (lambda result is: #{result})"
    end
    
    puts foo
    # inner proc
    puts foo2
    # foo (proc result is: proc) 
    puts bar
    # bar (lambda result is: lambda) 
    

    【讨论】:

    • "这是 Procs 的语义,不一定是所有块的语义。"它是 Proc.new 创建的 Proc 实例以及所有“普通”块(即不与 proclambda 关键字一起使用的块)的语义。
    • 没错,我可以添加一个示例,但我认为我的示例已经足够复杂了。
    • 由于某种原因,转换为块的 lambda 就像它们总是块一样:[1].map &lambda{return "lambda"} 从函数返回。这是 JRuby 中的错误吗?
    • puts foo2 将返回 foo2 (proc result is: proc)
    【解决方案3】:

    这样想:Proc.new 只是创建一个代码块,它是调用函数的一部分。 proc/lambda 创建一个具有特殊绑定的匿名函数。一些代码示例会有所帮助:

    def foo
      f = Proc.new { return "return from foo from inside Proc.new" }
      f.call # control leaves foo here
      return "return from foo" 
    end
    

    等价于

    def foo
      begin
        return "return from foo from inside begin/end" }
      end
    
      return "return from foo" 
    end
    

    所以很明显返回只会从函数'foo'返回

    相反:

    def foo
      f = proc { return "return from foo from inside proc" }
      f.call # control stasy in foo here
      return "return from foo" 
    end
    

    等价于(忽略此示例中未使用的绑定):

    def unonymous_proc
      return "return from foo from inside proc"
    end
    
    def foo
      unonymous_proc()
      return "return from foo" 
    end
    

    显然不会从 foo 返回,而是继续下一条语句。

    【讨论】:

    • 尽管这是一个老问题,但请注意 Ruby 1.8.7 和 1.9.3 之间存在差异,后者 Kernel.proc 的行为类似于 Proc.new 而不是 lambda。
    猜你喜欢
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 2019-06-24
    • 1970-01-01
    • 2019-06-13
    • 2017-01-13
    • 2015-08-10
    相关资源
    最近更新 更多