Как вырваться из рубинового блока?

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.

user169930
источник

Ответы:

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
JRL
источник
2
спасибо, но next только переходит к следующему элементу в массиве. можно ли выйти?
user169930 10.09.09
Вы должны позвонить дальше с возвращаемым значением. "def f; x = yield; ставит x; end" "f делать следующие 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..
Майк Граф
Одна очень интересная вещь. break somethingработает, break(something)работает, но true && break(somehting)выдает синтаксическую ошибку. Просто к вашему сведению. Если условие необходимо, то ifили unlessнужно использовать.
Акостадинов
21

используйте ключевое слово breakвместоreturn

AShelly
источник
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

Г. Аллен Моррис III
источник
2
перерыв заканчивается немедленно - следующая продолжается до следующей итерации.
Бен Обин
-3

Чтобы вырваться из рубинового блока просто используйте returnключевое слово return if value.nil?

Кири Ме
источник
2
Не returnвыходит из функции?
ragerdl