Estoy tratando de usar Ruby 1.9.1 para un lenguaje de scripting incrustado, de modo que el código de "usuario final" se escriba en un bloque Ruby. Un problema con esto es que me gustaría que los usuarios pudieran 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 uso '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 a lambda, pero el resultado es el mismo.
ruby
lambda
return
proc-object
MetaFu
fuente
fuente
Respuestas:
Simplemente use
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 el valor del bloque y finaliza su llamada. Si su bloque fue llamado poryield
o.call
, entonces también sebreak
rompe de este iteradornext
devuelve el valor del bloque y finaliza su llamada. Si su bloque fue llamado poryield
o.call
, entoncesnext
devuelve el valor a la línea dondeyield
fue llamadofuente
No puedes hacer eso en Ruby.
La
return
palabra clave siempre regresa del método o lambda en el contexto actual. En bloques, volverá del método en el que se definió el cierre . No se puede hacer que regrese del método de llamada o lambda.El Rubyspec demuestra que este es de hecho el comportamiento correcto para Ruby (es cierto que no es una implementación real, pero tiene como objetivo la 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 # ...
fuente
Lo estás mirando desde el punto de vista equivocado. Este es un problema de
thing
lambda, no.def thing(*args, &block) block.call.tap do |value| puts "value=#{value}" end end thing { 6 * 7 }
fuente
¿Dónde se invoca la 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
fuente
Tuve el mismo problema al escribir un DSL para un marco web en ruby ... (¡el marco web Anorexic será genial!) ...
de todos modos, busqué en las partes internas de ruby y encontré una solución simple usando el LocalJumpError devuelto cuando regresan las llamadas de Proc ... funciona bien en las pruebas hasta ahora, pero no estoy seguro de que sea completamente a prueba:
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 así:
if e.is_a? LocalJumpError
pero es un territorio desconocido para mí, así que me ceñiré a lo que probé hasta ahora.
fuente
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 usar return en sus procesos sin consecuencias, se conserva el self, etc.
La ventaja de usar Thread aquí es que, en algunos casos, no obtendrá LocalJumpError, y el retorno ocurrirá en el lugar más inesperado (en un método de nivel superior, omitiendo inesperadamente el resto de su cuerpo).
La principal desventaja es la sobrecarga potencial (puede reemplazar Thread + join con solo
yield
si eso es suficiente en su escenario).fuente
Encontré una forma, 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 }
fuente