ใน 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.new
proc ที่สร้างขึ้น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 ที่ดำเนินการในบริบทที่แตกต่างกัน
ความแตกต่างของพฤติกรรมด้วยreturn
IMHO คือความแตกต่างที่สำคัญที่สุดระหว่าง 2 ฉันชอบแลมบ์ดามากกว่าเพราะมันพิมพ์น้อยกว่า Proc.new :-)
proc {}
ขณะนี้สามารถสร้างขึ้นโดยใช้ ฉันไม่แน่ใจว่าเมื่อสิ่งนี้มีผล แต่มัน (เล็กน้อย) ง่ายกว่าการพิมพ์ Proc.new