ใน Ruby 1.8 มีความแตกต่างเล็กน้อยระหว่าง proc / lambda ในมือข้างหนึ่งและProc.newอีกด้านหนึ่ง
- ความแตกต่างเหล่านั้นคืออะไร?
- คุณสามารถให้แนวทางเกี่ยวกับวิธีการตัดสินใจเลือกได้หรือไม่?
- ใน Ruby 1.9 proc และ lambda นั้นแตกต่างกัน ตกลงคืออะไร?
ใน Ruby 1.8 มีความแตกต่างเล็กน้อยระหว่าง proc / lambda ในมือข้างหนึ่งและProc.newอีกด้านหนึ่ง
คำตอบ:
ข้อแตกต่างที่สำคัญ แต่แตกต่างกันเล็กน้อยระหว่าง procs ที่สร้างด้วยlambdaและ procs ที่สร้างด้วยProc.newคือวิธีจัดการกับreturnคำสั่ง:
lambda-created proc returnคำสั่งจะส่งกลับจาก proc เท่านั้นProc.new-created proc returnคำสั่งนั้นน่าแปลกใจมากกว่านี้เล็กน้อย: มันกลับมาควบคุมไม่เพียงแค่จาก proc เท่านั้น แต่ยังมาจากวิธีการที่ล้อมรอบ proc ด้วย!นี่คือการกระทำlambdaของ proc ที่สร้างreturnขึ้น มันทำงานในลักษณะที่คุณอาจคาดหวัง:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
ทีนี้นี่คือProc.newproc ที่สร้างขึ้นreturnทำสิ่งเดียวกัน คุณกำลังจะเห็นหนึ่งในกรณีเหล่านั้นที่รูบี้ทำลายหลักการเซอร์ไพรส์อย่างน้อยที่โอ้อวดมาก:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
ขอบคุณกับพฤติกรรมที่น่าแปลกใจนี้ (เช่นเดียวกับพิมพ์น้อยลง) ผมมักจะชอบใช้lambdaมากกว่าProc.newเมื่อมีการ procs
procวิธีการ มันเป็นแค่การจดชวเลขProc.newหรือเปล่า?
procเทียบเท่ากับProc.new
procทำตัวเหมือนlambdaและไม่ชอบProc.newเกี่ยวกับการส่งคืนข้อความ นั่นหมายความว่าทับทิมเอกสารไม่ถูกต้อง
procทำหน้าที่เหมือนlambdaใน 1.8 แต่ทำหน้าที่เหมือนProc.newใน 1.9 ดูคำตอบของ Peter Wagenet
lambdaเป็นวิธีที่ไม่ระบุชื่อ เนื่องจากเป็นเมธอดมันจะส่งคืนค่าและเมธอดที่เรียกว่าสามารถทำกับมันได้ทุกอย่างที่ต้องการรวมถึงการเพิกเฉยและคืนค่าที่แตกต่าง A Procเป็นเหมือนการวางโค้ดขนาดสั้น มันไม่ได้ทำหน้าที่เหมือนวิธีการ ดังนั้นเมื่อมีการส่งคืนเกิดขึ้นภายในProcนั่นเป็นเพียงส่วนหนึ่งของรหัสของวิธีการที่เรียกว่า
เพื่อให้คำชี้แจงเพิ่มเติม:
Joey กล่าวว่าพฤติกรรมการกลับมาของProc.newน่าแปลกใจ อย่างไรก็ตามเมื่อคุณพิจารณาว่า Proc.new ทำงานเหมือนบล็อกสิ่งนี้ไม่น่าแปลกใจเพราะนั่นเป็นวิธีการทำงานของบล็อก lambas ในทางกลับกันทำตัวเหมือนวิธีการมากกว่า
สิ่งนี้อธิบายได้ว่าทำไม Procs จึงมีความยืดหยุ่นเมื่อพูดถึง arity (จำนวนอาร์กิวเมนต์) ในขณะที่ lambdas ไม่ใช่ บล็อกไม่จำเป็นต้องระบุอาร์กิวเมนต์ทั้งหมด แต่จะใช้วิธีการนั้น ในขณะที่การระบุค่าเริ่มต้นของอาร์กิวเมนต์ lambda ไม่ใช่ตัวเลือกใน Ruby 1.8 ตอนนี้ได้รับการสนับสนุนใน Ruby 1.9 ด้วยไวยากรณ์ lambda ทางเลือก (ดังที่บันทึกไว้โดย webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
และ Michiel de Mare (OP) ไม่ถูกต้องเกี่ยวกับ Procs และแลมบ์ดาทำตัวเหมือนกันกับ arity ใน Ruby 1.9 ฉันได้ตรวจสอบแล้วว่าพวกเขายังคงรักษาพฤติกรรมจาก 1.8 ตามที่ระบุไว้ข้างต้น
breakคำแถลงไม่สมเหตุสมผลจริงๆทั้งใน Procs หรือ lambdas ใน Procs การแบ่งจะส่งคืนให้คุณจาก Proc.new ที่เสร็จสมบูรณ์แล้ว และมันก็ไม่สมเหตุสมผลเลยที่จะแยกออกจากแลมบ์ดาเพราะมันเป็นวิธีการหนึ่งและคุณจะไม่มีวันแตกจากระดับสูงสุดของวิธีการ
next, redoและraiseประพฤติตนเหมือนกันทั้งใน Procs และ lambdas ในขณะที่retryไม่ได้รับอนุญาตในทั้งสองและจะยกข้อยกเว้น
และสุดท้ายprocไม่ควรใช้วิธีนี้เนื่องจากไม่สอดคล้องกันและมีพฤติกรรมที่ไม่คาดคิด ใน Ruby 1.8 มันจะส่งคืนแลมบ์ดาจริง! ใน Ruby 1.9 สิ่งนี้ได้รับการแก้ไขแล้วและส่งคืน Proc หากคุณต้องการสร้าง Proc ให้ทำProc.newตาม
สำหรับข้อมูลเพิ่มเติมฉันขอแนะนำของThe Ruby Programming Languageของ O'Reilly ซึ่งเป็นแหล่งข้อมูลส่วนใหญ่ของฉันสำหรับข้อมูลนี้
breakจากยก Procs LocalJumpErrorขณะที่breakจาก lambdas พฤติกรรมเช่นเดียวreturn( เช่น , return nil)
ฉันพบหน้านี้ซึ่งแสดงความแตกต่างระหว่างProc.newและlambdaคือ ตามหน้าเว็บที่แตกต่างเพียงว่าแลมบ์ดาเป็นที่เข้มงวดเกี่ยวกับจำนวนของข้อโต้แย้งยอมรับในขณะที่แปลงข้อโต้แย้งไปProc.new nilนี่คือตัวอย่างเซสชัน IRB ที่แสดงให้เห็นถึงความแตกต่าง:
irb (main): 001: 0> l = lambda {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (main): 002: 0> p = Proc.new {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (หลัก): 003: 0> l.call "hello", "world"
=> "helloworld"
irb (หลัก): 004: 0> p.call "hello", "world"
=> "helloworld"
irb (หลัก): 005: 0> l.call "hello"
ArgumentError: จำนวนอาร์กิวเมนต์ไม่ถูกต้อง (1 สำหรับ 2)
จาก (irb): 1
จาก (irb): 5: ใน `call '
จาก (irb): 5
จาก: 0
irb (หลัก): 006: 0> p.call "hello"
TypeError: ไม่สามารถแปลงศูนย์เป็นสตริงได้
จาก (irb): 2: ใน `+ '
จาก (irb): 2
จาก (irb): 6: ใน `call '
จาก (irb): 6
จาก: 0
หน้าแนะนำให้ใช้แลมบ์ดาเว้นแต่ว่าคุณต้องการพฤติกรรมการยอมรับข้อผิดพลาดโดยเฉพาะ ฉันเห็นด้วยกับความรู้สึกนี้ การใช้แลมบ์ดาดูเหมือนจะรัดกุมกว่าและด้วยความแตกต่างที่ไม่มีนัยสำคัญดังกล่าวดูเหมือนว่าทางเลือกที่ดีกว่าในสถานการณ์โดยเฉลี่ย
สำหรับ Ruby 1.9 ขอโทษฉันยังไม่ได้ดูที่ 1.9 แต่ฉันไม่คิดว่าพวกเขาจะเปลี่ยนมันทั้งหมดมาก (อย่าใช้คำพูดของฉันมันดูเหมือนว่าคุณเคยได้ยินการเปลี่ยนแปลงบางอย่างดังนั้น ฉันอาจจะผิดที่นั่น)
Proc มีอายุมากกว่า แต่ความหมายของการกลับมาเป็นสิ่งที่ต่อต้านได้ง่ายสำหรับฉัน (อย่างน้อยตอนที่ฉันเรียนรู้ภาษา) เพราะ:
แลมบ์ดาปลอดภัยกว่าและใช้งานง่ายกว่า - ฉันมักจะใช้มันแทน proc
ฉันไม่สามารถพูดอะไรได้มากมายเกี่ยวกับความแตกต่างที่ลึกซึ้ง อย่างไรก็ตามฉันสามารถชี้ให้เห็นว่า Ruby 1.9 ตอนนี้อนุญาตพารามิเตอร์ทางเลือกสำหรับ lambdas และบล็อก
นี่คือไวยากรณ์ใหม่สำหรับ lambdas ของ stabby ต่ำกว่า 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 ไม่มีไวยากรณ์นั้น วิธีการทั่วไปในการประกาศบล็อก / lambdas ไม่รองรับ args เสริม:
# under 1.8
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
อย่างไรก็ตาม Ruby 1.9 ยังสนับสนุนอาร์กิวเมนต์ที่เป็นทางเลือกแม้จะมีไวยากรณ์เก่า:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
หากคุณต้องการสร้าง Ruby1.9 สำหรับ Leopard หรือ Linux ตรวจสอบบทความนี้ (การส่งเสริมตนเองที่ไร้ยางอาย)
คำตอบสั้น ๆ : สิ่งที่สำคัญคือทำอะไรreturn: แลมบ์ดากลับมาจากตัวของมันเองและพรานกลับมาจากตัวมันเองและฟังก์ชั่นที่เรียกมันว่า
สิ่งที่ชัดเจนน้อยกว่าคือสาเหตุที่คุณต้องการใช้แต่ละอย่าง แลมบ์ดาเป็นสิ่งที่เราคาดหวังว่าสิ่งต่าง ๆ ควรทำในลักษณะการเขียนโปรแกรมที่ใช้งานได้ มันเป็นวิธีการที่ไม่ระบุชื่อโดยมีขอบเขตปัจจุบันถูกผูกไว้โดยอัตโนมัติ ในทั้งสองแลมบ์ดาเป็นสิ่งที่คุณควรใช้
ในทางกลับกัน Proc มีประโยชน์อย่างยิ่งสำหรับการใช้งานภาษาเอง ตัวอย่างเช่นคุณสามารถใช้คำสั่ง "ถ้า" หรือ "สำหรับ" ลูปกับพวกเขา การส่งคืนใด ๆ ที่พบใน proc จะส่งคืนจากวิธีการที่เรียกว่าไม่ใช่แค่คำสั่ง "if" นี่คือวิธีการทำงานของภาษาการใช้คำว่า "ถ้า" ดังนั้นการเดาของฉันคือ Ruby ใช้สิ่งนี้ภายใต้หน้าปกและพวกเขาก็เปิดเผยเพราะมันดูทรงพลัง
คุณจะต้องใช้สิ่งนี้จริงๆถ้าคุณกำลังสร้างโครงสร้างภาษาใหม่เช่นลูป, โครงสร้างอื่น ๆ , เป็นต้น
วิธีที่ดีในการดูคือ lambdas จะถูกดำเนินการในขอบเขตของตัวเอง (ราวกับว่าเป็นการเรียกใช้เมธอด) ในขณะที่ Procs อาจถูกมองว่าดำเนินการแบบอินไลน์ด้วยวิธีการโทรอย่างน้อยก็เป็นวิธีที่ดีในการตัดสินใจเลือก ในแต่ละกรณี.
ฉันไม่ได้สังเกตเห็นความคิดเห็นใด ๆ เกี่ยวกับวิธีที่สามในเควส "proc" ซึ่งเลิกใช้แล้ว แต่จัดการแตกต่างกันใน 1.8 และ 1.9
นี่เป็นตัวอย่างที่ค่อนข้างชัดเจนซึ่งทำให้ง่ายต่อการเห็นความแตกต่างระหว่างการเรียกที่คล้ายกันทั้งสาม:
def meth1
puts "method start"
pr = lambda { return }
pr.call
puts "method end"
end
def meth2
puts "method start"
pr = Proc.new { return }
pr.call
puts "method end"
end
def meth3
puts "method start"
pr = proc { return }
pr.call
puts "method end"
end
puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
procคืนแลมบ์ดาเป็น 1.8 ตอนนี้ได้รับการแก้ไขแล้วให้ส่งคืน proc ใน 1.9 - อย่างไรก็ตามนี่เป็นการเปลี่ยนแปลงที่ผิดพลาด ดังนั้นจึงไม่แนะนำให้ใช้อีกต่อไป
Closures in Rubyเป็นภาพรวมที่ดีเกี่ยวกับวิธีการทำงานของบล็อกแลมบ์ดาและ proc ใน Ruby กับ Ruby
แลมบ์ดาทำงานได้ตามที่คาดหวังเช่นในภาษาอื่น ๆ
สายProc.newมีความประหลาดใจและสับสน
returnคำสั่งใน proc สร้างขึ้นโดยProc.newไม่เพียง แต่จะกลับมาควบคุมที่มาจากตัวเอง แต่ยังมาจากวิธีการที่ล้อมรอบมัน
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
คุณสามารถยืนยันว่าการProc.newแทรกรหัสลงในวิธีการปิดล้อมเช่นเดียวกับบล็อก แต่Proc.newสร้างวัตถุในขณะที่บล็อกเป็นส่วนหนึ่งของวัตถุ
และมีความแตกต่างระหว่างแลมบ์ดาและProc.newซึ่งก็คือการจัดการกับข้อโต้แย้ง (ผิด) แลมบ์ดาบ่นเกี่ยวกับมันในขณะที่Proc.newไม่สนใจข้อโต้แย้งเพิ่มเติมหรือพิจารณาว่าไม่มีข้อโต้แย้งใด ๆ
irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):21:in `block in irb_binding'
from (irb):25:in `call'
from (irb):25
from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
from (irb):47:in `block in irb_binding'
from (irb):49:in `call'
from (irb):49
from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"
BTW procใน Ruby 1.8 จะสร้างแลมบ์ดาในขณะที่ Ruby 1.9+ จะมีพฤติกรรมเช่นProc.newนี้ซึ่งทำให้เกิดความสับสน
เพื่ออธิบายรายละเอียดเกี่ยวกับการตอบสนองของ Accordion Guy:
ขอให้สังเกตว่าProc.newสร้าง proc ออกโดยการผ่านบล็อก ฉันเชื่อว่าlambda {...}มีการแยกวิเคราะห์เป็นตัวอักษรมากกว่าการเรียกเมธอดที่ผ่านบล็อก returnไอเอ็นจีจากภายในบล็อกที่แนบมากับการเรียกวิธีการจะกลับมาจากวิธีการไม่บล็อกและProc.newกรณีที่เป็นตัวอย่างของเรื่องนี้ที่เล่น
(นี่คือ 1.8. ฉันไม่รู้ว่าสิ่งนี้แปลเป็น 1.9.)
ฉันสายไปนิดหน่อย แต่มีสิ่งหนึ่งที่ดี แต่ไม่ค่อยมีใครรู้จักเกี่ยวกับการProc.newไม่พูดถึงในความคิดเห็นเลย ตามเอกสาร :
Proc::newอาจถูกเรียกโดยไม่มีบล็อกเฉพาะภายในวิธีการที่มีบล็อกที่แนบมาซึ่งในกรณีนั้นบล็อกนั้นจะถูกแปลงเป็นProcวัตถุ
ที่กล่าวว่าProc.newช่วยให้วิธีการห่วงโซ่ผลผลิต:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&blockข้อโต้แย้งในdefแต่ไม่ต้องทำเช่นนั้นในรายการหาเรื่อง
มันคุ้มค่าที่เน้นว่าreturnในผลตอบแทน proc จากวิธี lexically ล้อมรอบคือวิธีการที่ proc ที่ถูกสร้างขึ้น , ไม่ได้วิธีการที่เรียกว่า proc นี่คือผลที่ตามมาของคุณสมบัติการปิดของ procs ดังนั้นรหัสต่อไปนี้ไม่มีอะไรแสดงผล:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
แม้ว่า proc รันในfoobarมันถูกสร้างขึ้นในfooและเพื่อให้returnออกไม่เพียงfoo foobarดังที่ Charles Caldwell เขียนไว้ด้านบนมันให้ความรู้สึกแบบ GOTO ในความคิดของฉันreturnมันเป็นเรื่องปกติในบล็อกที่ดำเนินการในบริบทของคำศัพท์ แต่มีความเป็นธรรมชาติน้อยกว่าเมื่อใช้ใน proc ที่ดำเนินการในบริบทที่แตกต่างกัน
ความแตกต่างของพฤติกรรมด้วยreturnIMHO คือความแตกต่างที่สำคัญที่สุดระหว่าง 2 ฉันชอบแลมบ์ดามากกว่าเพราะมันพิมพ์น้อยกว่า Proc.new :-)
proc {}ขณะนี้สามารถสร้างขึ้นโดยใช้ ฉันไม่แน่ใจว่าเมื่อสิ่งนี้มีผล แต่มัน (เล็กน้อย) ง่ายกว่าการพิมพ์ Proc.new