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.