Ruby Lambda与Proc的区别:LocalJumpError

4

我是一个刚接触Ruby和StackOverflow的新手,在学习Ruby的过程中遇到了第一个重要的难题。我在理解Proc和Lambda方面遇到了很大的困难。以下是我正在处理的代码。

def procBuilder(message)
  Proc.new{ puts message; return}
end

def test
  puts "entering method"
  p = procBuilder("entering proc")
  p.call
  puts "exit method"
end

test

根据设计,这会抛出一个LocalJumpError,但我不太明白为什么会这样。如果我要猜测它做了什么,我会猜测它最初在运行时会打印"entering proc",然后在p.call中抛出错误,因为p.call没有传递任何字符串,但显然我错过了两行之间发生的一些关键事情。我也不完全明白为什么这个lambda表达式可以工作而proc却不行,但我想理解这个错误也将解决这个问题。
感谢您提供的澄清信息。

请纠正我,但我认为这与return语句有关。在过程中使用return将尝试从调用方法返回。因此,p.call尝试从testprocbuilder返回。 - abhinav
2个回答

5
这是我给一个相关问题的答案。它谈到了lambda和proc以及LocalJumpErrors。
在proc中,return是一种特殊的语法,它返回proc所在的词法作用域,而不是proc本身。因此它试图从已经退出的procBuilder中返回。
有几种方法可以解决这个问题:
  1. 根本不使用return。Ruby会自动将控制权返回给proc的调用者。
  2. proc更改为lambda,它的行为符合你的期望。Lambda的行为类似于方法;procs的行为类似于块。
至于你期望的错误,你不应该得到那个错误。procBuilder返回一个包含message变量的proc。你不需要将任何参数传递给proc本身。 编辑: 回答您的附加问题。这个 proc 是一个闭包。它已经“捕获”了消息变量(在 procBuilder 中的局部变量),当 proc 被创建时,它在作用域内。现在,proc 可以在程序中漫游,而消息变量则隐藏在其中,准备在调用时打印。唯一的问题是返回语句,它还有额外的要求,即词法作用域仍然是“活动的”。
这样做的原因是这种行为在块中非常有帮助。在这种情况下,它根本没有帮助,所以你应该使用一个lambda,其中返回意味着更少的疯狂。
Ruby 中闭包的真正好教程: http://innig.net/software/ruby/closures-in-ruby.rb

谢谢Isaac,但我还是有点困惑。当你说“一个特殊的语法片段,它从调用该过程的方法返回,而不是过程本身”时,你是告诉我它会跳到我的测试方法的结尾和程序的结尾,对吗?为什么这是一个错误而不仅仅是程序的结束呢? - Tim Lindsey
谢谢你的帮助,但我仍然很困惑(相信我,我已经使劲磕了好几次头)。如果 proc 已经在它到达 p.call 之前运行并返回,并且测试方法已经退出,则 p.call 如何能够将“进入 proc”打印到控制台? - Tim Lindsey
我稍微编辑了一下。我不得不修复原始答案的一点,并回答了你的附加问题。 - user24359
我想我大致明白了,也许是"LocalJumpError"这个术语让我有些困惑。感谢您的所有帮助。 - Tim Lindsey

0

proc和method或lambda之间的一个重要区别在于它们处理return语句的方式。如果一个方法被定义在另一个方法内部,内部方法中的return语句仅从内部方法本身退出,然后外部方法继续执行。同样的规则适用于在lambda内部定义lambda、在方法内部定义lambda或在lambda内部定义方法的情况。然而,当一个proc在方法内部被定义时,return语句将会从proc以及外部(封闭)方法中退出。例如:

def meditate
    puts "Adjusting posture…"
    p = Proc.new { puts "Ringing bell…"; return }
    p.call
    puts "Sitting still…"  # This is not executed
end

meditate

Output:
Adjusting posture…
Ringing bell…

请注意,由于过程(proc)内部的返回语句已经退出了过程(proc)和包含它的方法,所以方法的最后一行没有被执行。
如果我们定义一个没有外部方法(enclosing(outer) method)并使用返回(return)语句的过程(proc),它将会抛出一个LocalJumpError。
p = Proc.new { puts "Ringing bell…"; return }
p.call
Output:
Ringing bell…
LocalJumpError: unexpected return

这是因为当在一个过程中遇到返回语句时,它会从该过程被定义的范围中返回,而不是从调用该过程的上下文中返回。在以下示例中,由于该过程试图从顶层环境(即它所在的定义位置)返回,导致出现了LocalJumpError异常。
def meditate p
    puts "Adjusting posture…"
    p.call
    puts "Sitting still…"  # This is not executed
end

p = Proc.new { puts "Ringing bell…"; return }

meditate p
Output:
Adjusting posture…
Ringing bell…
LocalJumpError: unexpected return

通常,在proc中使用return语句并不是一个好主意。Procs通常在方法之间传递,如果定义proc的方法已经返回,它将抛出异常。在下面的示例中,我们可以删除return语句。但是,有些情况下我们确实需要返回一些东西。在这种情况下,最好使用lambda而不是proc。我们将在后面看到,lambda以一种不同的方式处理return语句,更像方法。
以下是涉及return语句的另一种情况:
def zafu_factory
    # This method will return the following proc implicitly
    Proc.new { puts "Round black zafu"; return }
end

def meditate
    puts "Adjusting posture…"
    p = zafu_factory
    p.call
    puts "Sitting still…"  # This is not executed
end

meditate
Output:
Adjusting posture…
Round black zafu
LocalJumpError: unexpected return

发生了什么?zafu_factory方法创建并隐式返回了一个proc。然后,meditate方法调用了该proc,当proc中的return语句被执行时,它试图从定义它的上下文(即zafu_factory方法)返回。但是,zafu_factory已经返回了proc,并且每次调用方法只能返回一次。换句话说,由于在调用proc时zafu_factory方法已经返回,因此尝试第二次返回时会抛出异常。
请参阅有关Ruby中的Procs和Lambdas:闭包的博客文章以获取更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接