¿Cómo salir de un bloque de rubíes?

420

Aqui esta Bar#do_things:

class Bar   
  def do_things
    Foo.some_method(x) do |x|
      y = x.do_something
      return y_is_bad if y.bad? # how do i tell it to stop and return do_things? 
      y.do_something_else
    end
    keep_doing_more_things
  end
end

Y aquí está Foo#some_method:

class Foo
  def self.some_method(targets, &block)
    targets.each do |target|
      begin
        r = yield(target)
      rescue 
        failed << target
      end
    end
  end
end

Pensé en usar raise, pero estoy tratando de hacerlo genérico, así que no quiero poner nada específico Foo.

usuario169930
fuente

Respuestas:

747

Usa la palabra clave next. Si no desea continuar con el siguiente elemento, utilícelo break.

Cuando nextse usa dentro de un bloque, hace que el bloque salga inmediatamente, devolviendo el control al método iterador, que luego puede comenzar una nueva iteración invocando el bloque nuevamente:

f.each do |line|              # Iterate over the lines in file f
  next if line[0,1] == "#"    # If this line is a comment, go to the next
  puts eval(line)
end

Cuando se usa en un bloque, breaktransfiere el control fuera del bloque, fuera del iterador que invocó el bloque y a la primera expresión después de la invocación del iterador:

f.each do |line|             # Iterate over the lines in file f
  break if line == "quit\n"  # If this break statement is executed...
  puts eval(line)
end
puts "Good bye"              # ...then control is transferred here

Y finalmente, el uso de returnen un bloque:

return siempre hace que el método de cierre regrese, independientemente de cuán profundamente anidado esté dentro de los bloques (excepto en el caso de lambdas):

def find(array, target)
  array.each_with_index do |element,index|
    return index if (element == target)  # return from find
  end
  nil  # If we didn't find the element, return nil
end
JRL
fuente
2
gracias, pero siguiente solo se mueve al siguiente elemento de la matriz. ¿Es posible salir?
user169930
Debe llamar a continuación con el valor de retorno. "def f; x = rendimiento; pone x; fin" "f hacer los siguientes 3; pone" o "; fin" Esto imprime 3 (pero no "o") en la consola.
Marcel Jackwerth
55
next, break, return, No se puede comparar
finiteloop
Agregué una respuesta expandiendo el comentario que @MarcelJackwerth agregó sobre el uso nexto breakcon un argumento.
Tyler Holien
8
También hay una palabra clave llamada redo, que básicamente solo mueve la ejecución de regreso a la parte superior del bloque dentro de la iteración actual.
Ajedi32
59

Solo quería salir de un bloque, algo así como un goto hacia adelante, no realmente relacionado con un bucle. De hecho, quiero romper un bloque que está en un bucle sin terminar el bucle. Para hacer eso, hice el bloque un ciclo de una iteración:

for b in 1..2 do
    puts b
    begin
        puts 'want this to run'
        break
        puts 'but not this'
    end while false
    puts 'also want this to run'
end

Espero que esto ayude al próximo googler que aterriza aquí en función de la línea de asunto.

Don Law
fuente
44
Esta fue la única respuesta que respondió la pregunta tal como fue formulada. Merece más puntos. Gracias.
Alex Nye
Esto funciona igual para el descanso y el siguiente. Si lo falso se cambia a verdadero, el siguiente permanecerá en la apariencia y se romperá.
G. Allen Morris III
39

Si desea que su bloque para devolver un valor de uso (por ejemplo, cuando se utiliza #map, #injectetc.), nexty breaktambién aceptan un argumento.

Considera lo siguiente:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    if x % 3 == 0
      count + 2
    elsif x.odd?
      count + 1
    else 
      count
    end
  end
end

El equivalente usando next:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    next count if x.even?
    next (count + 2) if x % 3 == 0
    count + 1
  end
end

Por supuesto, siempre puede extraer la lógica necesaria en un método y llamarla desde dentro de su bloque:

def contrived_example(numbers)
  numbers.inject(0) { |count, x| count + extracted_logic(x) }
end

def extracted_logic(x)
  return 0 if x.even?
  return 2 if x % 3 == 0
  1
end
Tyler Holien
fuente
1
Gracias por la pista con el argumento para break!
rkallensee
2
¿podría actualizar con un ejemplo específico usando break(probablemente solo reemplace uno de sus nextcon break..
Mike Graf
Una cosa muy interesante. break somethingfunciona, break(something)funciona pero true && break(somehting)produce un error de sintaxis. Solo para tu información. Si se necesita condición, entonces ifo se unlessdebe usar.
akostadinov
21

use la palabra clave en breaklugar dereturn

AShelly
fuente
8

Quizás pueda usar los métodos incorporados para encontrar elementos particulares en una matriz, en lugar de hacer each-ing targetsy hacer todo a mano. Algunos ejemplos:

class Array
  def first_frog
    detect {|i| i =~ /frog/ }
  end

  def last_frog
    select {|i| i =~ /frog/ }.last
  end
end

p ["dog", "cat", "godzilla", "dogfrog", "woot", "catfrog"].first_frog
# => "dogfrog"
p ["hats", "coats"].first_frog
# => nil
p ["houses", "frogcars", "bottles", "superfrogs"].last_frog
# => "superfrogs"

Un ejemplo sería hacer algo como esto:

class Bar
  def do_things
    Foo.some_method(x) do |i|
      # only valid `targets` here, yay.
    end
  end
end

class Foo
  def self.failed
    @failed ||= []
  end

  def self.some_method(targets, &block)
    targets.reject {|t| t.do_something.bad? }.each(&block)
  end
end
August Lilleaas
fuente
2
¡No agregue métodos arbitrarios como ese a la clase Array! Esa es realmente una mala práctica.
Mrbrdo
3
Rails lo hace, así que ¿por qué no puede?
wberry
2
@wberry Eso no quiere decir que necesariamente debería . ;) En general, sin embargo, es mejor evitar las clases principales de parches de mono a menos que tenga una razón bastante buena (es decir, agregar algunas funcionalidades generalizables muy útiles que muchos otros códigos encontrarán útiles). Incluso entonces, camina con cuidado, porque una vez que una clase tiene muchos parches de mono, es fácil para las bibliotecas comenzar a caminar unas sobre otras y causar un comportamiento extremadamente extraño.
blm768
2

next¡y breakparece hacer lo correcto en este ejemplo simplificado!

class Bar
  def self.do_things
      Foo.some_method(1..10) do |x|
            next if x == 2
            break if x == 9
            print "#{x} "
      end
  end
end

class Foo
    def self.some_method(targets, &block)
      targets.each do |target|
        begin
          r = yield(target)
        rescue  => x
          puts "rescue #{x}"
        end
     end
   end
end

Bar.do_things

salida: 1 3 4 5 6 7 8

G. Allen Morris III
fuente
2
el descanso finaliza inmediatamente: el siguiente continúa a la siguiente iteración.
Ben Aubin
-3

Para salir de un bloque de rubí, simplemente use la returnpalabra clave return if value.nil?

Kiry Meas
fuente
2
¿No returnsale de la función?
ragerdl