Як вирватися з рубінового блоку?


420

Ось 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

А ось 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

Я думав про використання рейзу, але намагаюся зробити це загальним, тому не хочу вносити нічого конкретного Foo.

Відповіді:


747

Використовуйте ключове слово next. Якщо ви не хочете переходити до наступного пункту, використовуйте break.

При nextвикористанні всередині блоку він призводить до негайного виходу блоку, повертаючи керування до методу ітератора, який потім може почати нову ітерацію, викликавши блок знову:

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передає керування з блоку, з ітератора, який викликав блок, і в перший вираз після виклику ітератора:

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

І нарешті, використання returnблоку:

return завжди викликає повернення методу, що обгороджує, незалежно від того, наскільки він глибоко вкладений у блоки (за винятком випадку лямбда):

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
дякую, але наступний переходить лише до наступного елемента в масиві. чи можна вийти?
користувач169930

Ви повинні дзвонити поруч із значенням повернення. "def f; x = урожай; ставить x; end" "f do next 3; ставить" o "; end" Це друкує 3 (але не "o") на консолі.
Марсель Джекверт

5
next, break, return, Ви не можете порівнювати
finiteloop

Я додав відповідь, що розширює коментар @MarcelJackwerth, доданий про використання nextабо breakаргумент.
Тайлер Холіен

8
Існує також ключове слово під назвою redo, яке в основному просто переміщує виконання верху до вершини блоку в межах поточної ітерації.
Ajedi32

59

Я хотів просто змогти вирватися з блоку - подібний до переходу вперед, не дуже пов’язаного з циклом. Насправді, я хочу зламати блок, який знаходиться в циклі, не закінчуючи цикл. Для цього я зробив блок одноітераційним циклом:

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

Сподіваємось, це допомагає наступному гуглеру, який розташований тут, на основі рядка теми.


4
Це була єдина відповідь, яка відповіла на питання так, як було поставлено. Заслуговує більше балів. Дякую.
Алекс Най

Це працює однаково і для перерви, і для наступної. Якщо помилкове значення буде змінено на справжнє, наступне залишиться на виду, а вибух вибухне.
Г. Аллен Морріс III

39

Якщо ви хочете , щоб ваш блок , щоб повернути корисне значення (наприклад , при використанні #map, #injectі т.д.), nextа breakтакож приймає аргумент.

Розглянемо наступне:

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

Еквівалент з використанням 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

Звичайно, ви завжди можете витягти потрібну логіку в метод і викликати її всередині свого блоку:

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
Дякуємо за підказку з аргументом за break!
rkallensee

2
НЕ могли б ви , будь ласка , оновлення ж / приклад конкретних використання break(ймовірно , просто замінити один з ваших nextз break..
Mike Graf

Одна дуже цікава річ. break somethingпрацює, break(something)працює, але true && break(somehting)видає синтаксичну помилку. Просто FYI. Якщо потрібна умова, то ifабо її unlessпотрібно використовувати.
акостадінов


8

Можливо, ви можете використовувати вбудовані методи пошуку конкретних елементів у масиві, а не each-ing targetsі робити все вручну. Кілька прикладів:

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"

Одним із прикладів може бути щось подібне:

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
Не додайте довільні методи, такі як до класу Array! Це дійсно погана практика.
mrbrdo

3
Rails це робить, так чому він не може?
wberry

2
@wberry Це не означає, що це обов'язково повинно . ;) Взагалі, однак, краще уникати основних патч-класів, якщо ви не маєте досить вагомих причин (тобто додайте дуже корисний узагальнюючий функціонал, який буде корисним для багатьох інших кодів). Вже тоді легенько ступайте, бо колись клас сильно пошкоджений мавпою, бібліотекам легко почати ходити один над одним і викликати надзвичайно дивну поведінку.
blm768

2

nextі, breakздається, робимо правильно в цьому спрощеному прикладі!

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

вихід: 1 3 4 5 6 7 8


2
перерва закінчується негайно - далі продовжується наступна ітерація.
Бен Аубін

-3

Щоб вийти з блоку рубіну, просто використовуйте returnключове слово return if value.nil?


2
Не returnвиходить із функції?
ragerdl
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.