วิธีการแยกออกจากบล็อกทับทิม?


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 ทำให้วิธีการปิดล้อมกลับมาเสมอโดยไม่คำนึงว่าการซ้อนกันภายในบล็อกเป็นอย่างไร (ยกเว้นในกรณีของ 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

2
ขอบคุณ แต่ถัดไปย้ายไปยังรายการถัดไปในอาร์เรย์ เป็นไปได้ไหมที่จะออก?
user169930

คุณต้องโทรถัดไปด้วยค่าส่งคืน "def f; x = yield; ทำให้ x; end" "f ทำ 3 ต่อไป; ใส่" o "; end" นี่จะพิมพ์ 3 (แต่ไม่มี "o") บนคอนโซล
Marcel Jackwerth

5
next, break, returnคุณไม่สามารถเปรียบเทียบ
finiteloop

ฉันได้เพิ่มคำตอบที่ขยายความคิดเห็น @MarcelJackwerth เพิ่มเกี่ยวกับการใช้nextหรือbreakมีข้อโต้แย้ง
Tyler Holien

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

หวังว่านี่จะช่วยชาว Google คนต่อไปที่มาถึงที่นี่ตามหัวข้อ


4
นี่เป็นคำตอบเดียวที่ตอบคำถามตามที่วางไว้ สมควรได้รับคะแนนมากขึ้น ขอบคุณ
Alex Nye

ใช้งานได้เหมือนกันสำหรับการหยุดพักและการต่อไป หากความผิดได้เปลี่ยนเป็นความจริงแล้วสิ่งต่อไปจะยังคงอยู่ในรูปลักษณ์และการแตกหักจะแตกออก
G. Allen Morris 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
คุณสามารถโปรดอัปเดต w / ตัวอย่างที่เฉพาะเจาะจงโดยใช้break(อาจจะเพียงแค่เปลี่ยนหนึ่งของคุณnextด้วยbreak..
ไมค์กราฟ

สิ่งหนึ่งที่น่าสนใจมาก break somethingทำงานได้break(something)ผล แต่ได้true && break(somehting)ข้อผิดพลาดทางไวยากรณ์ เพียงแค่ FYI ถ้าเงื่อนไขเป็นสิ่งจำเป็นแล้วifหรือunlessความต้องการที่จะใช้
akostadinov


8

บางทีคุณอาจจะสามารถใช้ในตัววิธีการสำหรับการค้นหารายการโดยเฉพาะอย่างยิ่งในอาร์เรย์แทนของeachไอเอ็นจี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 นั่นไม่ได้หมายความว่าควรจะเป็นอย่างนั้น ;) โดยทั่วไปแล้วมันเป็นการดีที่สุดที่จะหลีกเลี่ยงคลาสการปะของลิงถ้าคุณไม่มีเหตุผลที่ดี (เช่นการเพิ่มฟังก์ชั่นgeneralizable ที่มีประโยชน์มากซึ่งโค้ดอื่น ๆ จำนวนมากจะมีประโยชน์) แม้กระนั้นเบา ๆ เพราะเมื่อชั้นเรียนมีการปะติดลิงอย่างหนักมันเป็นเรื่องง่ายสำหรับห้องสมุดที่จะเริ่มเดินข้ามซึ่งกันและกันและก่อให้เกิดพฤติกรรมแปลก ๆ
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
การแตกจะหยุดลงในทันที - ถัดไปจะทำซ้ำ
เบ็น Aubin

-3

หากต้องการแยกออกจากบล็อกทับทิมเพียงใช้returnคำหลัก return if value.nil?


2
ไม่ได้returnออกจากฟังก์ชั่น?
ragerdl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.