แล้วคุณจะใช้อันไหนดีกว่าอีกอัน?
แล้วคุณจะใช้อันไหนดีกว่าอีกอัน?
คำตอบ:
ข้อแตกต่างประการหนึ่งคือวิธีที่พวกเขาจัดการกับข้อโต้แย้ง การสร้าง proc ที่ใช้proc {}
และProc.new {}
เทียบเท่า อย่างไรก็ตามการใช้lambda {}
จะให้ proc ที่ตรวจสอบจำนวนอาร์กิวเมนต์ที่ส่งไปให้ จากri Kernel#lambda
:
เทียบเท่ากับProc.newยกเว้นวัตถุ Proc ที่เป็นผลลัพธ์จะตรวจสอบจำนวนพารามิเตอร์ที่ส่งผ่านเมื่อถูกเรียก
ตัวอย่าง:
p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
p.call 1, 2 # => 5
p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass
p.call 1, 2, 3 # => 5
l = lambda {|a, b| puts a**2+b**2 } # => #<Proc:0x15016c@(irb):5 (lambda)>
l.call 1, 2 # => 5
l.call 1 # => ArgumentError: wrong number of arguments (1 for 2)
l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2)
นอกจากนี้เมื่อเคนชี้ให้เห็นการใช้return
ภายในแลมบ์ดาจะส่งกลับค่าของแลมบ์ดานั้น แต่การใช้return
ใน proc จะส่งคืนจากบล็อกที่ล้อมรอบ
lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return
ดังนั้นสำหรับการใช้งานที่รวดเร็วที่สุดที่พวกเขากำลังเดียวกัน แต่ถ้าคุณต้องการการตรวจสอบอย่างเข้มงวดอาร์กิวเมนต์อัตโนมัติ (ซึ่งบางครั้งยังสามารถช่วยเหลือเกี่ยวกับการแก้จุดบกพร่อง) หรือหากคุณจำเป็นต้องใช้return
คำสั่งให้กลับค่าของ proc lambda
ที่ใช้
ความแตกต่างที่แท้จริงระหว่าง procs และ lambdas นั้นมีทุกอย่างเกี่ยวกับคีย์เวิร์ดโฟลว์ควบคุม ฉันกำลังพูดคุยเกี่ยวกับreturn
, raise
, break
, redo
, retry
ฯลฯ - คำควบคุมเหล่านั้น สมมติว่าคุณมีคำสั่งคืนสินค้าใน proc เมื่อคุณโทร proc ของคุณมันจะไม่เพียง แต่เทคุณออกจากมัน แต่จะกลับมาจากวิธีการปิดล้อมเช่น:
def my_method
puts "before proc"
my_proc = Proc.new do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
ขั้นตอนสุดท้ายputs
ในวิธีการนั้นไม่เคยถูกดำเนินการเพราะเมื่อเราเรียก proc ของเราสิ่งที่return
อยู่ภายในนั้นก็จะทำให้เราหมดไป อย่างไรก็ตามหากเราแปลง proc ของเราเป็นแลมบ์ดาเราจะได้รับสิ่งต่อไปนี้:
def my_method
puts "before proc"
my_proc = lambda do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
after proc
การส่งคืนภายในแลมบ์ดาทำให้เราทิ้งตัวแลมบ์ดาเท่านั้นและวิธีการปิดล้อมยังคงดำเนินการต่อไป วิธีการควบคุมโฟลว์คีย์เวิร์ดถูกจัดการภายใน procs และ lambdas คือความแตกต่างที่สำคัญระหว่างคำเหล่านั้น
มีความแตกต่างที่สำคัญเพียงสอง
lambda
ตรวจสอบจำนวนข้อโต้แย้งที่ส่งไปให้ในขณะที่ a proc
ไม่ นี่หมายความว่า a lambda
จะโยนข้อผิดพลาดหากคุณผ่านจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้องในขณะที่ a proc
จะเพิกเฉยต่อข้อโต้แย้งที่ไม่คาดคิดและกำหนดให้nil
กับสิ่งที่ขาดไปlambda
ผลตอบแทนจะผ่านการควบคุมกลับไปที่วิธีการโทร; เมื่อproc
ผลตอบแทนมันจะทำทันทีโดยไม่ต้องกลับไปที่วิธีการโทรหากต้องการดูวิธีการทำงานให้ดูที่รหัสด้านล่าง วิธีแรกเราเรียกproc
; lambda
สองสาย
def batman_ironman_proc
victor = Proc.new { return "Batman will win!" }
victor.call
"Iron Man will win!"
end
puts batman_ironman_proc # prints "Batman will win!"
def batman_ironman_lambda
victor = lambda { return "Batman will win!" }
victor.call
"Iron Man will win!"
end
puts batman_ironman_lambda # prints "Iron Man will win!"
มาดูกันproc
ว่า "แบทแมนจะเป็นผู้ชนะ!" นี่เป็นเพราะมันกลับมาทันทีโดยไม่ต้องกลับไปที่วิธีการ batman_ironman_proc
lambda
อย่างไรก็ตามเรากลับไปยังวิธีการหลังจากที่ถูกเรียกดังนั้นวิธีการส่งกลับรหัสสุดท้ายที่ประเมิน: "Iron Man จะชนะ!"
# ตัวอย่าง Proc
p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p) # The '&' tells ruby to turn the proc into a block
proc = Proc.new { puts "Hello World" }
proc.call
# แลมบ์ดาตัวอย่าง
lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)
lam = lambda { puts "Hello World" }
lam.call
ความแตกต่างระหว่าง Procs และ Lambdas
ก่อนที่ฉันจะพูดถึงความแตกต่างระหว่าง procs และ lambdas สิ่งสำคัญคือต้องพูดถึงว่ามันเป็นทั้ง Proc object
proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }
proc.class # returns 'Proc'
lam.class # returns 'Proc'
อย่างไรก็ตามแลมบ์ดานั้นเป็น 'รสชาติ' ที่แตกต่างกันของ procs ความแตกต่างเล็กน้อยนี้จะแสดงเมื่อส่งคืนวัตถุ
proc # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'
1. Lambdas ตรวจสอบจำนวนอาร์กิวเมนต์ในขณะที่ procs ไม่
lam = lambda { |x| puts x } # creates a lambda that takes 1 argument
lam.call(2) # prints out 2
lam.call # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3) # ArgumentError: wrong number of arguments (3 for 1)
ในทางตรงกันข้าม procs ไม่สนใจว่าพวกเขาจะผ่านข้อโต้แย้งผิดจำนวน
proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2) # prints out 2
proc.call # returns nil
proc.call(1,2,3) # prints out 1 and forgets about the extra arguments
2. Lambdas และ procs ใช้คำหลัก 'return' ต่างกัน
'return' ด้านในของแลมบ์ดาทำให้โค้ดอยู่ด้านนอกโค้ดแลมบ์ดา
def lambda_test
lam = lambda { return }
lam.call
puts "Hello world"
end
lambda_test # calling lambda_test prints 'Hello World'
'return' ด้านในของ proc จะทริกเกอร์โค้ดด้านนอกของเมธอดที่ proc กำลังถูกประมวลผล
def proc_test
proc = Proc.new { return }
proc.call
puts "Hello world"
end
proc_test # calling proc_test prints nothing
และเพื่อตอบแบบสอบถามอื่น ๆ ของคุณที่หนึ่งที่จะใช้และเมื่อใด ฉันจะติดตาม @jtbandes ตามที่เขาพูดถึง
ดังนั้นสำหรับการใช้งานที่รวดเร็วที่สุดพวกเขาก็เหมือนกัน แต่ถ้าคุณต้องการตรวจสอบการโต้เถียงอย่างเข้มงวดอัตโนมัติ (ซึ่งบางครั้งสามารถช่วยแก้จุดบกพร่องได้) หรือถ้าคุณต้องการใช้คำสั่ง return เพื่อส่งคืนค่าของ proc ให้ใช้แลมบ์ดา
โพสต์ต้นฉบับที่นี่
โดยทั่วไปแลมบ์ดานั้นพูดง่ายกว่าโพรคส์เพราะมันคล้ายกับวิธีการมากกว่า พวกมันค่อนข้างเข้มงวดเกี่ยวกับ arity และพวกเขาก็ออกไปเมื่อคุณโทรกลับ ด้วยเหตุนี้ Rubyists หลายคนจึงใช้ lambdas เป็นตัวเลือกแรกยกเว้นว่าพวกเขาต้องการคุณสมบัติเฉพาะของ procs
Procs:Proc
วัตถุของคลาส เช่นเดียวกับบล็อกพวกเขาจะถูกประเมินในขอบเขตที่กำหนด
แลมบ์ดา:วัตถุของคลาสProc
แต่แตกต่างอย่างละเอียดจาก procs ปกติ พวกเขากำลังปิดเช่นบล็อกและ procs และเช่นนั้นพวกเขากำลังประเมินอยู่ในขอบเขตที่พวกเขากำลังกำหนด
กำลังสร้าง Proc
a = Proc.new { |x| x 2 }
การสร้างแลมบ์ดา
b = lambda { |x| x 2
}
a = proc { |x| x 2 }
เป็นเช่นเดียวกับa = Proc.new { |x| x 2 }
นี่เป็นอีกวิธีที่จะเข้าใจสิ่งนี้
บล็อกเป็นกลุ่มของรหัสที่แนบมากับการเรียกร้องให้มีการเรียกวิธีการบนวัตถุ ในตัวอย่างด้านล่าง self เป็นอินสแตนซ์ของคลาสที่ไม่ระบุชื่อซึ่งสืบทอดมาจาก ActionView :: Base ในเฟรมเวิร์ก Rails (ซึ่งมีโมดูลผู้ช่วยเหลือจำนวนมาก) บัตรเป็นวิธีการที่เราเรียกตัวเอง เราผ่านการโต้แย้งกับวิธีการและจากนั้นเรามักจะแนบบล็อกไปที่จุดสิ้นสุดของวิธีการภาวนา:
self.card :contacts do |c|
// a chunk of valid ruby code
end
ตกลงดังนั้นเราจึงส่งรหัสของวิธีการ แต่เราจะใช้ประโยชน์จากบล็อกนี้ได้อย่างไร ทางเลือกหนึ่งคือการแปลงกลุ่มของรหัสเป็นวัตถุ Ruby มีสามวิธีในการแปลงกลุ่มของโค้ดให้เป็นวัตถุ
# lambda
> l = lambda { |a| a + 1 }
> l.call(1)
=> 2
# Proc.new
> l2= Proc.new { |a| a + 1 }
> l2.call(1)
=> 2
# & as the last method argument with a local variable name
def add(&block)
end
ในวิธีการด้านบน & แปลงบล็อกที่ส่งผ่านไปยังวิธีการเป็นวัตถุและเก็บวัตถุนั้นในบล็อกตัวแปรท้องถิ่น ในความเป็นจริงเราสามารถแสดงให้เห็นว่ามันมีพฤติกรรมเช่นเดียวกับแลมบ์ดาและ Proc.new:
def add(&block)
block
end
l3 = add { |a| a + 1 }
l3.call(1)
=> 2
นี้เป็นสิ่งสำคัญ. เมื่อคุณส่งบล็อกไปยังเมธอดและแปลงโดยใช้ & วัตถุที่สร้างจะใช้ Proc.new เพื่อทำการแปลง
โปรดทราบว่าฉันหลีกเลี่ยงการใช้ "proc" เป็นตัวเลือก นั่นเป็นเพราะว่า Ruby 1.8 มันเหมือนกับ lambda และใน Ruby 1.9 มันเหมือนกับ Proc.new และใน Ruby ทุกรุ่นที่ควรหลีกเลี่ยง
ดังนั้นคุณถามความแตกต่างระหว่างแลมบ์ดากับ Proc.new คืออะไร?
ประการแรกในแง่ของการผ่านพารามิเตอร์แลมบ์ดามีพฤติกรรมเหมือนการเรียกเมธอด มันจะเพิ่มข้อยกเว้นถ้าคุณผ่านจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้อง ในทางตรงกันข้าม Proc.new จะทำงานเหมือนการกำหนดแบบขนาน อาร์กิวเมนต์ที่ไม่ได้ใช้ทั้งหมดจะถูกแปลงเป็นศูนย์:
> l = lambda {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb47e40@(irb):19 (lambda)>
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)
> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21>
> l2.call(1)
1 +
ประการที่สองแลมบ์ดาและ Proc.new จัดการคำหลักส่งคืนแตกต่างกัน เมื่อคุณส่งคืนภายใน Proc.new จริง ๆ แล้วจะส่งคืนจากวิธีการล้อมรอบนั่นคือบริบทโดยรอบ เมื่อคุณกลับมาจากบล็อกแลมบ์ดามันจะกลับมาจากบล็อกไม่ใช่วิธีการปิดล้อม โดยพื้นฐานแล้วมันจะออกจากการเรียกไปยังบล็อกและดำเนินการต่อโดยใช้วิธีการปิดล้อมที่เหลือ
> def add(a,b)
l = Proc.new { return a + b}
l.call
puts "now exiting method"
end
> add(1,1)
=> 2 # NOTICE it never prints the message "now exiting method"
> def add(a,b)
l = lambda { return a + b }
l.call
puts "now exiting method"
end
> add(1,1)
=> now exiting method # NOTICE this time it prints the message "now exiting method"
ดังนั้นทำไมความแตกต่างของพฤติกรรมนี้ เหตุผลก็คือด้วย Proc.new เราสามารถใช้ตัววนซ้ำในบริบทของการล้อมรอบเมธอดและสรุปผลตรรกะ ดูตัวอย่างนี้:
> def print(max)
[1,2,3,4,5].each do |val|
puts val
return if val > max
end
end
> print(3)
1
2
3
4
เราคาดหวังว่าเมื่อเราเรียกใช้การส่งคืนภายในตัววนซ้ำมันจะกลับมาจากวิธีการปิดล้อม จำบล็อกที่ส่งไปยังตัววนซ้ำถูกแปลงเป็นวัตถุโดยใช้ Proc.new และนั่นคือสาเหตุที่เมื่อเราใช้ return มันจะออกจากวิธีการปิดล้อม
คุณสามารถคิดว่า lambdas เป็นวิธีการที่ไม่ระบุชื่อพวกเขาแยกแต่ละบล็อกของรหัสเป็นวัตถุที่สามารถปฏิบัติเหมือนวิธี ท้ายที่สุดลองนึกถึงแลมบ์ดาว่าเป็นวิธีการที่ผิดปกติและ Proc.new ทำตัวเหมือนโค้ดอินไลน์
โพสต์ที่เป็นประโยชน์เกี่ยวกับคู่มือทับทิม: บล็อก procs & lambdas
Procs กลับมาจากวิธีการปัจจุบันในขณะที่ lambdas กลับจาก lambda เอง
Procs ไม่สนใจจำนวนอาร์กิวเมนต์ที่ถูกต้องในขณะที่ lambdas จะเพิ่มข้อยกเว้น
ความแตกต่างระหว่าง proc และ lambda ก็คือ proc เป็นเพียงสำเนาของโค้ดที่มีการแทนที่อาร์กิวเมนต์ในขณะที่แลมบ์ดาเป็นฟังก์ชั่นเหมือนในภาษาอื่น (พฤติกรรมการส่งคืนตรวจสอบข้อโต้แย้ง)
return
ผลตอบแทนที่คำสั่งจากในเมื่อเทียบกับproc
lambda