ใช้ 'return' ในบล็อก Ruby


89

ฉันกำลังพยายามใช้ Ruby 1.9.1 สำหรับภาษาสคริปต์แบบฝังเพื่อให้โค้ด "end-user" ถูกเขียนในบล็อก Ruby ปัญหาหนึ่งของเรื่องนี้คือฉันต้องการให้ผู้ใช้สามารถใช้คีย์เวิร์ด 'return' ในบล็อกได้ดังนั้นพวกเขาจึงไม่จำเป็นต้องกังวลเกี่ยวกับค่าส่งคืนโดยปริยาย ด้วยเหตุนี้สิ่งนี้จึงเป็นสิ่งที่ฉันต้องการจะทำได้:

def thing(*args, &block)
  value = block.call
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

หากฉันใช้ 'return' ในตัวอย่างข้างต้นฉันจะได้รับ LocalJumpError ฉันทราบว่านี่เป็นเพราะบล็อกที่เป็นปัญหาเป็น Proc ไม่ใช่แลมบ์ดา รหัสจะใช้งานได้ถ้าฉันลบ 'return' ออก แต่ฉันอยากจะใช้ 'return' ในสถานการณ์นี้จริงๆ เป็นไปได้หรือไม่ ฉันได้ลองแปลงบล็อกเป็นแลมด้าแล้ว แต่ผลลัพธ์ก็เหมือนกัน


เหตุใดคุณจึงต้องการหลีกเลี่ยงค่าส่งคืนโดยปริยาย
marcgg

@marcgg - ฉันมีคำถามที่เกี่ยวข้องที่นี่ - stackoverflow.com/questions/25953519/… .
sid smith

คำตอบ:


173

เพียงใช้nextในบริบทนี้:

$ irb
irb(main):001:0> def thing(*args, &block)
irb(main):002:1>   value = block.call
irb(main):003:1>   puts "value=#{value}"
irb(main):004:1> end
=> nil
irb(main):005:0>
irb(main):006:0* thing {
irb(main):007:1*   return 6 * 7
irb(main):008:1> }
LocalJumpError: unexpected return
        from (irb):7:in `block in irb_binding'
        from (irb):2:in `call'
        from (irb):2:in `thing'
        from (irb):6
        from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>'
irb(main):009:0> thing { break 6 * 7 }
=> 42
irb(main):011:0> thing { next 6 * 7 }
value=42
=> nil
  • return มักจะคืนค่าจาก method แต่ถ้าคุณทดสอบ snippet นี้ใน irb คุณไม่มี method นั่นคือเหตุผลที่คุณมี LocalJumpError
  • breakส่งคืนค่าจากบล็อกและสิ้นสุดการโทร หากบล็อกของคุณถูกเรียกโดยyieldหรือ.callก็breakจะแตกจากตัววนซ้ำนี้ด้วย
  • nextส่งคืนค่าจากบล็อกและสิ้นสุดการโทร ถ้าบล็อกของคุณถูกเรียกโดยyieldหรือ.callให้nextส่งกลับค่าไปยังบรรทัดที่yieldถูกเรียก

4
การแตกใน proc จะทำให้เกิดข้อยกเว้น
gfreezy

คุณสามารถอ้างอิงว่าคุณได้รับข้อมูลนี้จากที่ใด "ส่งคืนค่าต่อไปจากบล็อกและสิ้นสุดการโทร" ฉันต้องการอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้
user566245

มันมาจากหนังสือ The Ruby Programming Language (ตอนนี้ยังไม่มี) ถ้าจำไม่ผิด ฉันเพิ่งตรวจสอบ Google และฉันเชื่อว่ามันมาจากหนังสือเล่มนั้น: librairie.immateriel.fr/fr/read_book/9780596516178/…และอีก 2 หน้าถัดไปจากที่นั่น (ไม่ใช่เนื้อหาและหน้าของฉันฉันแค่ googled มัน) แต่ฉันแนะนำหนังสือต้นฉบับจริงๆมันมีการอธิบายอัญมณีมากขึ้น
MBO

นอกจากนี้ฉันยังตอบจากหัวของฉันเพียงตรวจสอบสิ่งต่างๆใน irb นั่นคือเหตุผลที่คำตอบของฉันไม่เป็นเทคนิคหรือสมบูรณ์ สำหรับข้อมูลเพิ่มเติมโปรดดูหนังสือ The Ruby Programming Language
MBO

ฉันหวังว่าคำตอบนี้จะอยู่ด้านบนสุด ฉันไม่สามารถโหวตได้มากพอ
btx9000

20

คุณไม่สามารถทำได้ใน Ruby

returnคำหลักที่มักจะกลับมาจากวิธีการหรือแลมบ์ดาในบริบทปัจจุบัน ในบล็อกก็จะกลับมาจากวิธีการที่ปิดที่ถูกกำหนดไว้ ไม่สามารถส่งคืนจากวิธีการโทรหรือแลมด้าได้

Rubyspecแสดงให้เห็นว่านี่เป็นพฤติกรรมที่ถูกต้องสำหรับ Ruby (เป็นที่ยอมรับไม่ได้ดำเนินการจริง แต่มีจุดมุ่งหมายที่เข้ากันได้เต็มรูปแบบกับ C ทับทิม):

describe "The return keyword" do
# ...
describe "within a block" do
# ...
it "causes the method that lexically encloses the block to return" do
# ...
it "returns from the lexically enclosing method even in case of chained calls" do
# ...

มีบทความโดยละเอียดเกี่ยวกับการกลับมาจากบล็อก / proc ที่นี่
ComDubh


1

สิ่งใดถูกเรียกใช้? คุณอยู่ในชั้นเรียนหรือไม่?

คุณอาจลองใช้สิ่งนี้:

class MyThing
  def ret b
    @retval = b
  end

  def thing(*args, &block)
    implicit = block.call
    value = @retval || implicit
    puts "value=#{value}"
  end

  def example1
    thing do
      ret 5 * 6
      4
    end
  end

  def example2
    thing do
      5 * 6
    end
  end
end

1

ฉันมีปัญหาเดียวกันกับการเขียน DSL สำหรับเว็บเฟรมเวิร์กในทับทิม ... (เว็บเฟรมเวิร์ก Anorexic จะสั่นคลอน!) ...

อย่างไรก็ตามฉันขุดเข้าไปในรูบี้ภายในและพบวิธีแก้ปัญหาง่ายๆโดยใช้ LocalJumpError ส่งคืนเมื่อการโทร Proc กลับมา ... มันทำงานได้ดีในการทดสอบจนถึงตอนนี้ แต่ฉันไม่แน่ใจว่ามันพิสูจน์ได้ทั้งหมด:

def thing(*args, &block)
  if block
    block_response = nil
    begin
      block_response = block.call
    rescue Exception => e
       if e.message == "unexpected return"
          block_response = e.exit_value
       else
          raise e 
       end
    end
    puts "value=#{block_response}"
  else
    puts "no block given"
  end
end

คำสั่ง if ในส่วนการช่วยเหลืออาจมีลักษณะดังนี้:

if e.is_a? LocalJumpError

แต่มันเป็นดินแดนที่ไม่มีใครสังเกตเห็นสำหรับฉันดังนั้นฉันจะยึดติดกับสิ่งที่ฉันทดสอบจนถึงตอนนี้


1

ฉันเชื่อว่านี่เป็นคำตอบที่ถูกต้องแม้ว่าจะมีข้อเสียก็ตาม:

def return_wrap(&block)
  Thread.new { return yield }.join
rescue LocalJumpError => ex
  ex.exit_value
end

def thing(*args, &block)
  value = return_wrap(&block)
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

แฮ็คนี้ช่วยให้ผู้ใช้สามารถใช้ผลตอบแทนในการประมวลผลโดยไม่มีผลใด ๆ รักษาตัวเอง ฯลฯ

ข้อดีของการใช้ Thread ที่นี่คือในบางกรณีคุณจะไม่ได้รับ LocalJumpError - และการส่งคืนจะเกิดขึ้นในสถานที่ที่ไม่คาดคิดที่สุด (อยู่ข้างเมธอดระดับบนสุดโดยไม่คาดคิดข้ามส่วนที่เหลือของเนื้อหา)

ข้อเสียเปรียบหลักคือค่าโสหุ้ยที่เป็นไปได้ (คุณสามารถแทนที่ Thread + join ได้เพียงแค่yieldว่าเพียงพอในสถานการณ์ของคุณ)


1

ฉันพบวิธีหนึ่ง แต่เกี่ยวข้องกับการกำหนดวิธีการเป็นขั้นตอนกลาง:

def thing(*args, &block)
  define_method(:__thing, &block)
  puts "value=#{__thing}"
end

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