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


74
2018-02-24 11:15


origen


Respuestas:


Simplemente usa 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 valor del bloque y finaliza su llamada. Si tu bloque fue llamado por yield o .call, entonces break se rompe de este iterador también
  • next devuelve valor del bloque y finaliza su llamada. Si tu bloque fue llamado por yield o .call, entonces next devuelve valor a la línea donde yield fue llamado

153
2018-02-24 11:55



No puedes hacer eso en Ruby.

los return palabra clave siempre regresa del método o lambda en el contexto actual. En bloques, regresará del método en el que se realizó el cierre definido. No se puede hacer que regrese del vocación método o lambda.

los Rubyspec demuestra que este es de hecho el comportamiento correcto para Ruby (ciertamente 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
# ...

15
2018-02-24 11:42



Lo estás mirando 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
}

3
2018-02-24 11:33



¿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

1
2018-02-24 11:32



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 tu escenario).


1
2018-05-27 15:16



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.


0
2017-10-11 20:55



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 }

0
2017-07-27 14:29