Что такое улов и бросок, используемый в Ruby?
В большинстве других языков заявления catch и throw делают то, что делают инструкции begin, rescue и raise в Ruby. Я знаю, что вы можете сделать это с помощью этих двух утверждений:
catch :done do
puts "I'm done."
end
и
if some_condition
throw :done
end
Но для чего это полезно? Может кто-нибудь, пожалуйста, дайте мне пример того, какие слова catch и throw используются для Ruby?
Ответы
Ответ 1
Вы можете использовать это, чтобы вырваться из вложенных циклов.
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 будет действительно полезен. Я использовал здесь, чтобы решить одну из проблем Эйлера.
Ответ 2
Я искал хороший пример на некоторое время, пока не встретил Синатру.
IMHO, Sinatra предоставляет очень интересный пример использования для catch
.
В Sinatra вы можете немедленно прекратить запрос в любое время, используя halt
.
halt
Вы также можете указать статус при остановке...
halt 410
Или тело...
halt 'this will be the body'
Или оба...
halt 401, 'go away!'
Метод halt реализован с использованием throw.
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
и поймали метод invoke
.
В Синатре существует несколько вариантов использования :halt
. Вы можете прочитать исходный код для получения дополнительных примеров.
Ответ 3
При написании рекурсивных алгоритмов, которые действуют на вложенные структуры данных с использованием рекурсивных функций, вы можете использовать throw
аналогично тому, как использовать break
или ранний return
при написании итерационных алгоритмов, работающих на плоских данных, используя for
.
Например, предположим, что у вас есть список положительных целых чисел, и вы хотите (по какой-то причине) написать функцию, которая вернет true, если выполнено одно из следующих условий:
- Сумма всех элементов в списке больше 100
- Некоторый элемент в списке, если он равен 5
Скажем также, что вы всегда хотите выполнить эту проверку в одном коротком замыкающем проходе над списком, вместо того, чтобы делать вызов reduce
, чтобы получить сумму и отдельный вызов any?
для поиска пяти.
Вероятно, вы, вероятно, напишите некоторый код (например, вы, вероятно, в какой-то момент своей жизни написали такой код на каком-то языке):
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
, например:
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
. На других языках вы можете сделать это либо путем злоупотребления исключениями для этой цели, либо с помощью некоторого кода возврата, чтобы сообщить рекурсивной функции, чтобы остановить рекурсию, но это более прямо и просто.