What is catch and throw used for in Ruby?
In most other languages, the catch and throw statements do what the begin, rescue, and raise statements do in Ruby. I know the you can do this with these two statements:开发者_如何学Go
catch :done do
puts "I'm done."
end
and
if some_condition
throw :done
end
But what is this useful for? Can somebody please give me an example of what catch and throw statements are used for in Ruby?
You can use this to break out of nested loops.
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
If you had used a break statement above, it would have broken out of the inner loop. But if you want to break out of the nested loop, then this catch/throw would be really helpful. I have used it here to solve one of the Euler problems.
I have been looking for a good example for a while, until I met Sinatra.
IMHO, Sinatra exposes a very interesting example usage for catch
.
In Sinatra you can immediately terminate a request at any time using halt
.
halt
You can also specify the status when halting...
halt 410
Or the body...
halt 'this will be the body'
Or both...
halt 401, 'go away!'
The halt method is implemented using throw.
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
and caught by the invoke
method.
There are several uses of :halt
in Sinatra. You can read the source code for more examples.
When writing recursive algorithms that act on nested data structures using recursive functions, you can use throw
similarly to how you would use a break
or early return
when writing iterative algorithms that act on flat data using for
loops.
For example, suppose that you have a list of positive integers and you want (for some reason) to write a function that will return true if either of the following conditions are met:
- The sum of all elements in the list is greater than 100
- Some element in the list if equal to 5
Let's say also that you always want to perform this check in a single, short-circuiting pass over the list, rather than doing a reduce
call to get the sum and a separate any?
call to look for fives.
You'd probably write some code a bit like this (indeed, you probably HAVE written code like this in some language at some point in your life):
def test(list)
sum = 0
for i in list
sum += i
if i == 5 || sum > 100
return true
end
end
return false
end
In most languages, there is no clean equivalent for breaking out of a recursive algorithm that uses a recursive function. In Ruby, though, there is! Suppose that, instead of having a list and wanting to check if its elements contain a five or sum to over 100, you have a tree and want to check if its leaves contain a five or sum to over 100, while short-circuiting and returning as soon as you know the answer.
You can do this elegantly with throw
/catch
, like this:
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
The throw :passes_test
here acts a bit like a break
; it lets you jump out of your whole call stack below the outermost _test
call. In other languages, you could do this either by abusing exceptions for this purpose or by using some return code to tell the recursive function to stop recursing, but this is more direct and simpler.
精彩评论