ความแตกต่างระหว่าง proc และ lambda ใน Ruby คืออะไร?


176

แล้วคุณจะใช้อันไหนดีกว่าอีกอัน?


นอกจากนี้ในการตอบ jtbandes ของนอกจากนี้ยังมีความแตกต่างในสิ่งที่เป็นreturnผลตอบแทนที่คำสั่งจากในเมื่อเทียบกับproc lambda
Ken Bloom

5
นี่คือหนึ่งบล็อกที่ดีในawaxman11.github.io/blog/2013/08/05/
Arup Rakshit

2
นี่คือคำตอบโดยละเอียดเพิ่มเติม: stackoverflow.com/questions/626/…
Dan KK

คำตอบ:


260

ข้อแตกต่างประการหนึ่งคือวิธีที่พวกเขาจัดการกับข้อโต้แย้ง การสร้าง 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ที่ใช้


8
มันจะถูกต้องหรือไม่ที่จะบอกว่า lambdas นั้นเป็นเหมือนวิธีการอย่างมาก (ตรวจสอบข้อโต้แย้งและผลตอบแทนจะกลับมาจากพวกเขา) ในขณะที่ procs เป็นเหมือนบล็อก (อาร์กิวเมนต์ไม่ได้ถูกตรวจสอบและกลับมาจะกลับมาจาก
pedz

ฉันเคยไปถึงพระเจ้าแล้วรู้ว่ามีเว็บไซต์และบทความจำนวนเท่าไรในตอนนี้และดูเหมือนว่าไม่มีใครพูดถึงประโยชน์ของ Procs เทียบกับวิธีการกับ lambdas คำอธิบายทุกข้อจะให้รายละเอียดเกี่ยวกับการคืนค่า ฯลฯ ที่แตกต่างกัน แต่ไม่มีเหตุผลว่าทำไมจึงสำคัญ สำหรับตอนนี้ฉันต้องสรุปว่านี่เป็นงานออกแบบใน Ruby
ankush981

76

ความแตกต่างที่แท้จริงระหว่าง 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 คือความแตกต่างที่สำคัญระหว่างคำเหล่านั้น


7

มีความแตกต่างที่สำคัญเพียงสอง

  • ก่อนอื่นให้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 จะชนะ!"


5

# ตัวอย่าง 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 ให้ใช้แลมบ์ดา

โพสต์ต้นฉบับที่นี่


1

โดยทั่วไปแลมบ์ดานั้นพูดง่ายกว่าโพรคส์เพราะมันคล้ายกับวิธีการมากกว่า พวกมันค่อนข้างเข้มงวดเกี่ยวกับ 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 }
lacostenycoder

1

นี่เป็นอีกวิธีที่จะเข้าใจสิ่งนี้

บล็อกเป็นกลุ่มของรหัสที่แนบมากับการเรียกร้องให้มีการเรียกวิธีการบนวัตถุ ในตัวอย่างด้านล่าง 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 ทำตัวเหมือนโค้ดอินไลน์


0

โพสต์ที่เป็นประโยชน์เกี่ยวกับคู่มือทับทิม: บล็อก procs & lambdas

Procs กลับมาจากวิธีการปัจจุบันในขณะที่ lambdas กลับจาก lambda เอง

Procs ไม่สนใจจำนวนอาร์กิวเมนต์ที่ถูกต้องในขณะที่ lambdas จะเพิ่มข้อยกเว้น


-3

ความแตกต่างระหว่าง proc และ lambda ก็คือ proc เป็นเพียงสำเนาของโค้ดที่มีการแทนที่อาร์กิวเมนต์ในขณะที่แลมบ์ดาเป็นฟังก์ชั่นเหมือนในภาษาอื่น (พฤติกรรมการส่งคืนตรวจสอบข้อโต้แย้ง)

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