comentarios - Usando ''return'' en un bloque Ruby
codigo ruby ejemplo (7)
¿Dónde se invoca cosa? ¿Estás dentro de una clase?
Puede considerar usar algo como esto:
class MyThing
def ret b
@retval = b
end
def thing(*args, &block)
implicit = block.call
value = @retval || implicit
puts "value=#{value}"
end
def example1
thing do
ret 5 * 6
4
end
end
def example2
thing do
5 * 6
end
end
end
Intento utilizar Ruby 1.9.1 para un lenguaje de scripts incorporado, de modo que el código del "usuario final" se escriba en un bloque de Ruby. Un problema con esto es que me gustaría que los usuarios puedan usar la palabra clave ''return'' en los bloques, para que no tengan que preocuparse por los valores de retorno implícitos. Con esto en mente, este es el tipo de cosas que me gustaría poder hacer:
def thing(*args, &block)
value = block.call
puts "value=#{value}"
end
thing {
return 6 * 7
}
Si utilizo ''return'' en el ejemplo anterior, obtengo un LocalJumpError. Soy consciente de que esto se debe a que el bloque en cuestión es un Proc y no un lambda. El código funciona si elimino ''return'', pero realmente preferiría poder usar ''return'' en este escenario. es posible? Intenté convertir el bloque en una lambda, pero el resultado es el mismo.
Creo que esta es la respuesta correcta, a pesar de los inconvenientes:
def return_wrap(&block)
Thread.new { return yield }.join
rescue LocalJumpError => ex
ex.exit_value
end
def thing(*args, &block)
value = return_wrap(&block)
puts "value=#{value}"
end
thing {
return 6 * 7
}
Este truco permite a los usuarios utilizar retorno en sus procesos sin consecuencias, se preserva, etc.
La ventaja de utilizar Thread aquí es que en algunos casos no obtendrá LocalJumpError, y el retorno ocurrirá en el lugar más inesperado (junto a un método de nivel superior, omitiendo el resto del cuerpo de forma inesperada).
La principal desventaja es la sobrecarga potencial (puede reemplazar el Thread + join con solo el yield
si eso es suficiente en su escenario).
Encontré una manera, pero implica definir un método como un paso intermedio:
def thing(*args, &block)
define_method(:__thing, &block)
puts "value=#{__thing}"
end
thing { return 6 * 7 }
Lo estás mirando desde el punto de vista equivocado. Esta es una cuestión de thing
, no la lambda.
def thing(*args, &block)
block.call.tap do |value|
puts "value=#{value}"
end
end
thing {
6 * 7
}
No puedes hacer eso en Ruby.
La palabra clave return
siempre regresa del método o lambda en el contexto actual. En bloques, regresará del método en el que se definió el cierre. No se puede hacer que regrese desde el método de llamada o lambda.
El Rubyspec demuestra que este es de hecho el comportamiento correcto para Ruby (no es una implementación real, pero apunta a una compatibilidad total con C Ruby):
describe "The return keyword" do
# ...
describe "within a block" do
# ...
it "causes the method that lexically encloses the block to return" do
# ...
it "returns from the lexically enclosing method even in case of chained calls" do
# ...
Simplemente use el next
en este contexto:
$ irb
irb(main):001:0> def thing(*args, &block)
irb(main):002:1> value = block.call
irb(main):003:1> puts "value=#{value}"
irb(main):004:1> end
=> nil
irb(main):005:0>
irb(main):006:0* thing {
irb(main):007:1* return 6 * 7
irb(main):008:1> }
LocalJumpError: unexpected return
from (irb):7:in `block in irb_binding''
from (irb):2:in `call''
from (irb):2:in `thing''
from (irb):6
from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>''
irb(main):009:0> thing { break 6 * 7 }
=> 42
irb(main):011:0> thing { next 6 * 7 }
value=42
=> nil
-
return
siempre regresa del método, pero si prueba este fragmento en irb no tiene método, es por eso que tieneLocalJumpError
-
break
devuelve valor del bloque y finaliza su llamada. Si tu bloque fue llamado poryield
o.call
, entonces también sebreak
rupturas de este iterador -
next
devuelve el valor del bloque y finaliza su llamada. Si su bloque fue llamado poryield
o.call
, entonces elnext
devuelve el valor a la línea donde se llamóyield
Tuve el mismo problema al escribir un DSL para un framework web en ruby ... (¡el framework web Anorexic rockeará!) ...
de todos modos, busqué en el interior de rubí y encontré una solución simple usando LocalJumpError devuelto cuando regresa una llamada de Proc ... funciona bien en las pruebas hasta el momento, pero no estoy seguro de que sea a prueba completa:
def thing(*args, &block)
if block
block_response = nil
begin
block_response = block.call
rescue Exception => e
if e.message == "unexpected return"
block_response = e.exit_value
else
raise e
end
end
puts "value=#{block_response}"
else
puts "no block given"
end
end
la declaración if en el segmento de rescate probablemente podría verse más o menos así:
if e.is_a? LocalJumpError
pero es un territorio desconocido para mí, así que me atengo a lo que probé hasta ahora.