2010-02-24 24 views
73

Estoy tratando de usar Ruby 1.9.1 para un lenguaje de scripting incrustado, para que el código de "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 ser capaz de hacer:Usando 'return' en un bloque de Ruby

def thing(*args, &block) 
    value = block.call 
    puts "value=#{value}" 
end 

thing { 
    return 6 * 7 
} 

Si uso 'retorno' en el ejemplo anterior, aparece 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.

+0

¿por qué quiere evitar un valor de retorno implícito? – marcgg

+0

@marcgg - Tengo una pregunta relacionada aquí - https://stackoverflow.com/questions/25953519/simple-way-to-understand-returning-from-a-block-in-ruby. –

Respuesta

1

¿Dónde se invoca algo? ¿Estás dentro de una clase?

Usted puede considerar el uso de 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 
3

Usted está buscando desde el punto de vista equivocado. Este es un problema de thing, no el lambda.

def thing(*args, &block) 
    block.call.tap do |value| 
    puts "value=#{value}" 
    end 
end 

thing { 
    6 * 7 
} 
14

No se puede hacer eso en Ruby.

La palabra clave returnsiempre regresa del método o lambda en el contexto actual. En bloques, volverá del método en el que el cierre fue definido como. No se puede hacer que regrese desde el llamando al método o lambda.

El Rubyspec demuestra que este es el comportamiento correcto para Ruby (la verdad no es una aplicación real, pero tiene como objetivo la plena compatibilidad con C Rubí):

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

Utilice simplemente 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 tiene LocalJumpError
  • break devuelve el valor del bloque y finaliza su llamada. Si su bloque fue llamado por yield o .call, entonces break descansos de este iterador demasiado
  • next declaraciones de valor a partir de bloque y termina su llamada. Si su bloque fue llamado por yield o .call, a continuación, vuelve next valor de línea en la que se llamó yield
+0

muy bien dicho. bien hecho. –

+4

romper en un proceso generará una excepción – gfreezy

+0

¿Puede citar de dónde obtiene esta información a partir de ese "próximo valor devuelve del bloque y finaliza su llamada". Quiero leer más sobre eso. – user566245

0

que tenía el mismo problema escribiendo un DSL para un framework de desarrollo web en Ruby ... (el framework web anoréxica will rock !) ...

de todos modos, busqué en el interior de ruby ​​y encontré una solución simple usando el LocalJumpError devuelto cuando una llamada de Proc regresa ...funciona bien en las pruebas hasta ahora, pero no estoy seguro de que es completa 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 sentencia if en el segmento de rescate probablemente podría ser algo como esto:

if e.is_a? LocalJumpError 

pero es territorio inexplorado para mí, así que me atengo a lo que probé hasta ahora.

1

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 el retorno en sus procs sin consecuencias, sí se conserva, etc.

La ventaja de usar El hilo aquí es que en algunos casos no obtendrá el 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).

0

he encontrado una manera, pero se trata de definir un método como un paso intermedio:

def thing(*args, &block) 
    define_method(:__thing, &block) 
    puts "value=#{__thing}" 
end 

thing { return 6 * 7 } 
Cuestiones relacionadas