Jak wydostać się z rubinowego bloku?


420

Oto 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

A oto 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

Myślałem o użyciu przebicia, ale staram się, aby było to ogólne, więc nie chcę dodawać nic konkretnego Foo.

Odpowiedzi:


747

Użyj słowa kluczowego next. Jeśli nie chcesz przejść do następnego elementu, użyj break.

Gdy nextjest używany w bloku, powoduje natychmiastowe wyjście z bloku, zwracając kontrolę do metody iteratora, która może następnie rozpocząć nową iterację poprzez ponowne wywołanie bloku:

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

W przypadku użycia w bloku breakprzekazuje kontrolę poza blok, z iteratora, który wywołał blok, i do pierwszego wyrażenia następującego po wywołaniu iteratora:

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

I wreszcie użycie returnw bloku:

return zawsze powoduje zwrócenie metody zamykającej, niezależnie od tego, jak głęboko jest zagnieżdżona w blokach (z wyjątkiem lambd):

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

2
dzięki, ale następny przechodzi tylko do następnego elementu w tablicy. czy można wyjść?
user169930

Następnie musisz zadzwonić z wartością zwracaną. "def f; x = wydajność; wstawia x; koniec" "f do następnych 3; wstawia" o "; koniec" To wypisuje 3 (ale nie "o") na konsoli.
Marcel Jackwerth,

5
next, break, return, Nie można porównać
finiteloop

Dodałem odpowiedź rozszerzającą komentarz dodany przez @MarcelJackwerth o używaniu nextlub breakz argumentem.
Tyler Holien

8
Istnieje również słowo kluczowe o nazwie redo, które po prostu przenosi wykonanie z powrotem na górę bloku w bieżącej iteracji.
Ajedi32

59

Chciałem po prostu móc wydostać się z bloku - coś w rodzaju goto do przodu, niezupełnie związanego z pętlą. W rzeczywistości chcę zerwać z bloku, który jest w pętli bez przerywania pętli. Aby to zrobić, utworzyłem blok w pętli jednoetapowej:

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

Mam nadzieję, że to pomoże następnemu Google'owi, który wyląduje tutaj na podstawie tematu.


4
To była jedyna odpowiedź, która odpowiedziała na postawione pytanie. Zasługuje na więcej punktów. Dzięki.
Alex Nye,

Działa to tak samo dla przerwy i następnej. Jeśli fałsz zostanie zmieniony na true, następny pozostanie w wyglądzie, a przerwa wybuchnie.
G. Allen Morris III

39

Jeśli chcesz, aby powrócić do bloku użyteczną wartość (na przykład przy użyciu #map, #injectetc.), nexta breaktakże przyjąć argumentu.

Rozważ następujące:

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

Odpowiednik przy użyciu 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

Oczywiście zawsze można wyodrębnić potrzebną logikę do metody i wywołać ją z wnętrza bloku:

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

1
Dzięki za podpowiedź z argumentem za break!
rkallensee

2
można zaktualizuj W / A przykład stosując specyficzne break(prawdopodobnie wystarczy wymienić jeden z twoich nextz break..
Mike Graf

Jedna bardzo interesująca rzecz. break somethingdziała, break(something)działa, ale true && break(somehting)powoduje błąd składniowy. Po prostu dla ciebie. Jeśli warunek jest potrzebne, to ifczy unlessnależy stosować.
akostadinov


8

Być może możesz użyć wbudowanych metod do znajdowania określonych elementów w tablicy, zamiast each-ing targetsi robienia wszystkiego ręcznie. Kilka przykładów:

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"

Jednym z przykładów może być zrobienie czegoś takiego:

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

2
Nie dodawaj takich arbitralnych metod do klasy Array! To naprawdę zła praktyka.
mrbrdo

3
Rails to robi, więc dlaczego nie może?
wberry

2
@wberry To nie znaczy, że koniecznie powinno . ;) Ogólnie jednak najlepiej jest unikać poprawiania podstawowych klas małp, chyba że masz całkiem dobry powód (tj. Dodając bardzo przydatną uogólniającą funkcjonalność, którą wiele innych kodów może uznać za przydatne). Nawet wtedy, stąpaj lekko, ponieważ gdy klasa jest mocno załatana małpami, biblioteki łatwo zaczynają się chodzić i wywoływać bardzo dziwne zachowania.
blm768

2

nexti breakwydaje się, że robi to poprawnie w tym uproszczonym przykładzie!

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

wyjście: 1 3 4 5 6 7 8


2
przerwa kończy się natychmiast - następny przechodzi do następnej iteracji.
Ben Aubin

-3

Aby wyjść z rubinowego bloku, wystarczy użyć returnsłowa kluczowego return if value.nil?


2
Nie returnwychodzi z funkcji?
ragerdl
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.