Что такое улов и бросок, используемый в 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. На других языках вы можете сделать это либо путем злоупотребления исключениями для этой цели, либо с помощью некоторого кода возврата, чтобы сообщить рекурсивной функции, чтобы остановить рекурсию, но это более прямо и просто.