section - ¿Para qué se utiliza la captura y el tiro en Ruby?
wbr tag in html5 (3)
Al escribir algoritmos recursivos que actúan en estructuras de datos anidadas usando funciones recursivas, puede usar throw
manera similar a como usaría un break
o return
temprano al escribir algoritmos iterativos que actúan en datos planos usando bucles.
Por ejemplo, supongamos que tiene una lista de enteros positivos y desea (por alguna razón) escribir una función que devuelva verdadero si se cumple alguna de las siguientes condiciones:
- La suma de todos los elementos en la lista es mayor que 100
- Algún elemento en la lista si es igual a 5
Digamos también que siempre desea realizar esta comprobación en una única pasada de corto circuito sobre la lista, en lugar de hacer una llamada de reduce
para obtener la suma y otra por separado any?
llamar para buscar cinco.
Probablemente escribirías un código como este (de hecho, probablemente HAYAS escrito un código como este en algún idioma en algún momento de tu vida):
def test(list)
sum = 0
for i in list
sum += i
if i == 5 || sum > 100
return true
end
end
return false
end
En la mayoría de los lenguajes, no existe un equivalente limpio para romper un algoritmo recursivo que usa una función recursiva. ¡En Ruby, sin embargo, hay! Supongamos que, en lugar de tener una lista y querer verificar si sus elementos contienen un cinco o una suma de más de 100, usted tiene un árbol y quiere verificar si sus hojas contienen un cinco o una suma de más de 100, mientras se cortocircuita y regresa tan pronto como sepa la respuesta.
Puedes hacerlo elegantemente con throw
/ catch
, así:
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
La throw :passes_test
aquí actúa un poco como un break
; le permite saltar de toda su pila de llamadas por debajo de la llamada de _test
más _test
. En otros idiomas, puede hacer esto abusando de las excepciones para este propósito o usando algún código de retorno para decirle a la función recursiva que deje de recurrir, pero esto es más directo y simple.
En la mayoría de los otros idiomas, las declaraciones de captura y lanzamiento hacen lo que hacen las declaraciones de inicio, rescate y elevación en Ruby. Sé que puedes hacer esto con estas dos afirmaciones:
catch :done do
puts "I''m done."
end
y
if some_condition
throw :done
end
¿Pero para qué sirve esto? ¿Alguien puede por favor darme un ejemplo de para qué se usan las declaraciones catch and throw en Ruby?
He estado buscando un buen ejemplo por un tiempo, hasta que conocí a Sinatra. En mi humilde opinión, Sinatra expone un ejemplo de uso muy interesante para la catch
.
En Sinatra, puede cancelar inmediatamente una solicitud en cualquier momento mediante la halt
.
halt
También puede especificar el estado cuando se detiene ...
halt 410
O el cuerpo ...
halt ''this will be the body''
O ambos...
halt 401, ''go away!''
El método de parada se implementa utilizando el tiro .
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
y atrapado por el método de invoke
.
Hay varios usos de :halt
en Sinatra. Puedes leer el código fuente para más ejemplos.
Puede usar esto para salir de los bucles anidados.
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
Si hubiera utilizado una declaración de ruptura anterior, se habría salido del bucle interno. Pero si quieres salir del ciclo anidado, entonces este catch / throw sería realmente útil. Lo he usado here para resolver uno de los problemas de Euler.