Wie kann man aus einem Rubinblock ausbrechen?

419

Hier ist 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

Und hier ist 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

Ich habe darüber nachgedacht, Raise zu verwenden, aber ich versuche, es generisch zu machen, also möchte ich nichts Spezielles hinzufügen Foo.

user169930
quelle

Antworten:

747

Verwenden Sie das Schlüsselwort next. Wenn Sie nicht mit dem nächsten Element fortfahren möchten, verwenden Sie break.

Wenn nextes innerhalb eines Blocks verwendet wird, wird der Block sofort beendet und die Steuerung an die Iteratormethode zurückgegeben, die dann eine neue Iteration beginnen kann, indem der Block erneut aufgerufen wird:

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

breakÜberträgt bei Verwendung in einem Block die Steuerung aus dem Block, aus dem Iterator, der den Block aufgerufen hat, und in den ersten Ausdruck nach dem Aufruf des Iterators:

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

Und schließlich die Verwendung returnin einem Block:

return bewirkt immer, dass die einschließende Methode zurückkehrt, unabhängig davon, wie tief sie in Blöcken verschachtelt ist (außer im Fall von 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
quelle
2
danke, aber next geht nur zum nächsten Element im Array. ist es möglich zu beenden?
user169930
Sie müssen als nächstes mit dem Rückgabewert aufrufen. "def f; x = Ausbeute; setzt x; Ende" "f macht die nächsten 3; setzt" o "; Ende" Dies gibt 3 (aber kein "o") auf der Konsole aus.
Marcel Jackwerth
5
next, break, returnKann man nicht vergleichen
finiteloop
Ich habe eine Antwort hinzugefügt, die den Kommentar @MarcelJackwerth über die Verwendung von nextoder breakmit einem Argument erweitert.
Tyler Holien
8
Es gibt auch ein Schlüsselwort namens redo, das die Ausführung innerhalb der aktuellen Iteration einfach wieder an den Anfang des Blocks verschiebt.
Ajedi32
59

Ich wollte nur in der Lage sein, aus einem Block auszubrechen - eine Art Vorwärts-Goto, der nicht wirklich mit einer Schleife zusammenhängt. Tatsächlich möchte ich einen Block, der sich in einer Schleife befindet, abbrechen, ohne die Schleife zu beenden. Zu diesem Zweck habe ich den Block zu einer Schleife mit einer Iteration gemacht:

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

Hoffe das hilft dem nächsten Googler, der hier landet, basierend auf der Betreffzeile.

Don Law
quelle
4
Dies war die einzige Antwort, die die gestellte Frage beantwortete. Verdient mehr Punkte. Vielen Dank.
Alex Nye
Dies funktioniert sowohl für die Pause als auch für die nächste Pause gleich. Wenn das Falsche in Wahr geändert wird, bleibt das nächste im Look und es kommt zu einem Ausbruch.
G. Allen Morris III
39

Wenn Sie Ihr Block einen nützlichen Wert zurückgeben (zB bei der Verwendung #map, #injectetc.), nextund breakauch ein Argument akzeptieren.

Folgendes berücksichtigen:

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

Das Äquivalent mit 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

Natürlich können Sie jederzeit die erforderliche Logik in eine Methode extrahieren und diese aus Ihrem Block heraus aufrufen:

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
quelle
1
Danke für den Hinweis mit dem Argument für break!
rkallensee
2
Könnten Sie bitte mit einem bestimmten Beispiel aktualisieren break(wahrscheinlich ersetzen Sie einfach eines Ihrer nextdurch break..
Mike Graf
Eine sehr interessante Sache. break somethingfunktioniert, break(something)funktioniert, true && break(somehting)ergibt aber einen Syntaxfehler. Nur zu deiner Information. Wenn eine Bedingung erforderlich ist, muss ifoder unlessmuss diese verwendet werden.
Akostadinov
21

Verwenden Sie das Schlüsselwort breakanstelle vonreturn

AShelly
quelle
8

Vielleicht können Sie die integrierten Methoden verwenden, um bestimmte Elemente in einem Array zu finden, anstatt alles von Hand zu eacherledigen targets. Einige Beispiele:

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"

Ein Beispiel wäre so etwas:

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
quelle
2
Fügen Sie der Array-Klasse keine solchen willkürlichen Methoden hinzu! Das ist wirklich schlechte Praxis.
Mrbrdo
3
Rails macht es, warum kann er es nicht?
Wberry
2
@wberry Das heißt nicht, dass es unbedingt sein sollte . ;) Im Allgemeinen ist es jedoch am besten, das Patchen von Affen-Kernklassen zu vermeiden, es sei denn, Sie haben einen guten Grund (dh Sie fügen einige sehr nützliche verallgemeinerbare Funktionen hinzu, die viele andere Codes nützlich finden). Selbst dann treten Sie leicht, denn sobald eine Klasse stark von Affen befleckt ist, können Bibliotheken leicht übereinander laufen und ein äußerst merkwürdiges Verhalten verursachen.
blm768
2

nextund breakscheinen in diesem vereinfachten Beispiel das Richtige zu tun!

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

Ausgabe: 1 3 4 5 6 7 8

G. Allen Morris III
quelle
2
Die Pause endet sofort - als nächstes wird mit der nächsten Iteration fortgefahren.
Ben Aubin
-3

Um aus einem Rubinblock auszubrechen, verwenden Sie einfach das returnSchlüsselwort return if value.nil?

Kiry Meas
quelle
2
Beendet returndie Funktion nicht?
Ragerdl