开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜