Usando ‘return’ en un bloque Ruby

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.

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 `
' 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 tiene LocalJumpError
  • break devuelve valor del bloque y finaliza su llamada. Si tu bloque fue llamado por yield o .call , entonces también se break rupturas de este iterador
  • next devuelve el valor del bloque y finaliza su llamada. Si su bloque fue llamado por yield o .call , entonces el next devuelve el valor a la línea donde se llamó yield

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 # ... 

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 } 

¿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 

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 rest 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).

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 Ruby 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 statement 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.

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 }