什么是Ruby中的catch和throw?

What is catch and throw used for in Ruby?

在大多数其他语言中,catch和throw语句执行begin,rescue和raise语句在Ruby中执行的操作。 我知道你可以用这两个陈述来做到这一点:

1
2
3
catch :done do
  puts"I'm done."
end

1
2
3
if some_condition
  throw :done
end

但这有用的是什么? 有人可以给我一个Ruby中使用catch和throw语句的例子吗?


您可以使用它来打破嵌套循环。

1
2
3
4
5
6
7
8
9
10
INFINITY = 1.0 / 0.0
catch (:done) do
  1.upto(INFINITY) do |i|
    1.upto(INFINITY) do |j|
      if some_condition
        throw :done
      end
    end
  end
end

如果你曾经使用过上面的break语句,它就会破坏内部循环。但是如果你想要打破嵌套循环,那么这个catch / throw会非常有用。我在这里用它来解决欧拉问题之一。


我一直在寻找一个很好的例子,直到我遇到Sinatra。
恕我直言,Sinatra为catch公开了一个非常有趣的示例用法。

在Sinatra中,您可以使用halt随时立即终止请求。

1
halt

您还可以在暂停时指定状态...

1
halt 410

还是身体......

1
halt 'this will be the body'

或两者...

1
halt 401, 'go away!'

halt方法使用throw实现。

1
2
3
4
def halt(*response)
  response = response.first if response.length == 1
  throw :halt, response
end

并被invoke方法捕获。

Sinatra中有:halt的几种用法。您可以阅读源代码以获取更多示例。


在编写使用递归函数作用于嵌套数据结构的递归算法时,您可以使用throw,类似于在编写使用for循环对平面数据执行的迭代算法时如何使用break或早期return

例如,假设您有一个正整数列表,并且您希望(出于某种原因)编写一个函数,如果满足以下任一条件,该函数将返回true:

  • 列表中所有元素的总和大于100
  • 列表中的某些元素如果等于5

让我们说你总是希望在列表中的单个短路传递中执行此检查,而不是执行reduce调用以获取总和以及单独的any?调用以查找五个。

你可能会写一些像这样的代码(事实上,你可能已经在某些语言中用你的生活中的某些语言编写了这样的代码):

1
2
3
4
5
6
7
8
9
10
def test(list)
  sum = 0
  for i in list
    sum += i
    if i == 5 || sum > 100
      return true
    end
  end
  return false
end

在大多数语言中,没有干净的等价物来打破使用递归函数的递归算法。但是在Ruby中,有!假设,您没有列表并且想要检查其元素是否包含五个或总和超过100,那么您有一棵树并且想要检查其叶子是否包含五或总和超过100,同时短路并返回一旦你知道了答案。

您可以使用throw / catch优雅地执行此操作,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def _test_recurse(sum_so_far, node)
  if node.is_a? InternalNode
    for child_node in node.children
      sum_so_far = _test_recurse(sum_so_far, child_node)
    end
    return sum_so_far
  else # node.is_a? Leaf
    sum_so_far += node.value
    if node.value == 5
      throw :passes_test
    elsif sum_so_far > 100
      throw :passes_test
    else
      return sum_so_far
    end
  end
end

def test(tree)            
  catch (:passes_test) do
    _test_recurse(0, tree)
    return false
  end
  return true
end

这里的throw :passes_test有点像break;它可以让你跳出最外面的_test调用下的整个调用堆栈。在其他语言中,您可以通过为此目的滥用异常或使用一些返回代码来告诉递归函数停止递归来执行此操作,但这更直接,更简单。