Sự khác biệt giữa một Proc và lambda trong Ruby là gì?


176

Và khi nào bạn sẽ sử dụng cái này chứ không phải cái kia?


Ngoài câu trả lời của jtbandes, còn có một sự khác biệt trong returncâu lệnh trả về từ procso với lambda.
Ken Bloom

5
Đây là một blog tốt trên cùng một awaxman11.github.io/blog/2013/08/05/ Khăn
Arup Rakshit

2
Dưới đây là câu trả lời chi tiết hơn: stackoverflow.com/questions/626/ Mạnh
Dan KK

Câu trả lời:


260

Một sự khác biệt là trong cách họ xử lý các đối số. Tạo một Proc sử dụng proc {}Proc.new {}tương đương. Tuy nhiên, việc sử dụng lambda {}cung cấp cho bạn một Proc kiểm tra số lượng đối số được truyền cho nó. Từ ri Kernel#lambda:

Tương đương với Proc.new , ngoại trừ các đối tượng Proc kết quả kiểm tra số lượng tham số được truyền khi được gọi.

Một ví dụ:

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)

Ngoài ra, như Ken chỉ ra, sử dụng returnbên trong lambda sẽ trả về giá trị của lambda đó, nhưng sử dụng returntrong một trả về Proc từ khối kèm theo.

lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return

Vì vậy, đối với hầu hết các mục đích sử dụng nhanh, chúng đều giống nhau, nhưng nếu bạn muốn kiểm tra đối số nghiêm ngặt tự động (đôi khi cũng có thể giúp gỡ lỗi) hoặc nếu bạn cần sử dụng returncâu lệnh để trả về giá trị của Proc, hãy sử dụng lambda.


8
Liệu có chính xác để nói rằng lambdas rất giống các phương thức (kiểm tra đối số và trả về sẽ trả về từ chúng) trong khi procs rất giống các khối (đối số không được kiểm tra và trả về sẽ trả về từ phương thức chứa hoặc lambda)?
pedz

Bây giờ tôi đã đến với Chúa biết có bao nhiêu trang web và bài viết và dường như không ai nói về tiện ích của Procs so với phương thức so với lambdas. Mỗi lời giải thích chỉ cung cấp một chi tiết chia tóc về cách các giá trị trả về, v.v., là khác nhau, nhưng không có lý do tại sao nó quan trọng. Bây giờ tôi phải kết luận rằng đây là một mớ hỗn độn trong Ruby.
ankush981

76

Sự khác biệt thực sự giữa procs và lambdas có mọi thứ để làm với các từ khóa dòng kiểm soát. Tôi đang nói về return, raise, break, redo, retryvv - những lời tầm kiểm soát. Giả sử bạn có một tuyên bố trở lại trong một Proc. Khi bạn gọi Proc của bạn, nó sẽ không chỉ loại bạn ra khỏi nó mà còn quay trở lại từ phương thức kèm theo, ví dụ:

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

Cuối cùng putstrong phương thức, không bao giờ được thực thi, vì khi chúng tôi gọi Proc của chúng tôi, returnbên trong nó đã loại chúng tôi ra khỏi phương thức. Tuy nhiên, nếu chúng tôi chuyển đổi Proc của chúng tôi thành lambda, chúng tôi nhận được như sau:

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

Sự trở lại trong lambda chỉ khiến chúng ta rời khỏi lambda và phương thức kèm theo tiếp tục thực thi. Cách từ khóa điều khiển được xử lý trong procs và lambdas là sự khác biệt chính giữa chúng


7

Chỉ có hai sự khác biệt chính.

  • Đầu tiên, a lambdakiểm tra số lượng đối số được truyền cho nó, trong khi a prockhông. Điều này có nghĩa là a lambdasẽ đưa ra lỗi nếu bạn chuyển sai số lượng đối số, trong khi đó procsẽ bỏ qua các đối số không mong muốn và gán nilcho bất kỳ đối số nào bị thiếu.
  • Thứ hai, khi lambdatrả về, nó chuyển điều khiển trở lại phương thức gọi; khi proctrả về, nó thực hiện ngay lập tức mà không cần quay lại phương thức gọi.

Để xem làm thế nào điều này hoạt động, hãy xem mã dưới đây. Phương pháp đầu tiên của chúng tôi gọi a proc; cuộc gọi thứ hai a 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!"

Xem cách procnói "Người dơi sẽ chiến thắng!", Điều này là do nó quay trở lại ngay lập tức mà không cần quay lại phương thức batman_ironman_proc.

lambdaTuy nhiên, chúng tôi quay lại phương thức sau khi được gọi, vì vậy phương thức trả về mã cuối cùng mà nó đánh giá: "Người sắt sẽ thắng!"


5

# Proc Ví dụ

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

# Ví dụ Lambda

lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call           

Sự khác biệt giữa Procs và Lambdas

Trước khi tôi đi vào sự khác biệt giữa procs và lambdas, điều quan trọng là phải đề cập rằng cả hai đều là đối tượng Proc.

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }

proc.class # returns 'Proc'
lam.class  # returns 'Proc'

Tuy nhiên, lambdas là một 'hương vị' khác của procs. Sự khác biệt nhỏ này được hiển thị khi trả lại các đối tượng.

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'

1. Lambdas kiểm tra số lượng đối số, trong khi procs thì không

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)

Ngược lại, procs không quan tâm nếu chúng được truyền sai số lượng đối số.

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 và procs đối xử với từ khóa 'return' khác nhau

'return' bên trong lambda kích hoạt mã ngay bên ngoài mã lambda

def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # calling lambda_test prints 'Hello World'

'return' bên trong một Proc sẽ kích hoạt mã bên ngoài phương thức mà Proc đang được thực thi

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # calling proc_test prints nothing

Và để trả lời câu hỏi khác của bạn, nên sử dụng câu hỏi nào và khi nào? Tôi sẽ theo dõi @jtbandes như anh ấy đã đề cập

Vì vậy, đối với hầu hết các mục đích sử dụng nhanh, chúng đều giống nhau, nhưng nếu bạn muốn kiểm tra đối số nghiêm ngặt tự động (đôi khi cũng có thể giúp gỡ lỗi) hoặc nếu bạn cần sử dụng câu lệnh return để trả về giá trị của Proc, hãy sử dụng lambda.

Ban đầu được đăng ở đây


1

Nói chung, lambdas trực quan hơn procs vì chúng giống với phương thức hơn. Họ khá nghiêm khắc về arity và họ chỉ cần thoát khi bạn gọi trở lại. Vì lý do này, nhiều người chơi Ruby sử dụng lambdas như một lựa chọn đầu tiên, trừ khi họ cần các tính năng cụ thể của procs.

Procs: Đối tượng của lớp Proc. Giống như các khối, chúng được đánh giá trong phạm vi chúng được xác định. Lambdas: Cũng là đối tượng của lớp Procnhưng khác biệt tinh tế với procs thông thường. Chúng đóng như khối và procs, và như vậy chúng được đánh giá trong phạm vi chúng được xác định.

Tạo Proc

a = Proc.new { |x| x 2 }

Tạo lambda

b = lambda { |x| x 2 }


a = proc { |x| x 2 }giống nhưa = Proc.new { |x| x 2 }
lacostenycoder

1

Đây là một cách khác để hiểu điều này.

Một khối là một đoạn mã được gắn vào lời gọi đến một cuộc gọi của một phương thức trên một đối tượng. Trong ví dụ dưới đây, self là một thể hiện của một lớp ẩn danh kế thừa từ ActionView :: Base trong khung Rails (bản thân nó bao gồm nhiều mô đun trợ giúp). Thẻ là một phương pháp chúng tôi tự gọi. Chúng ta truyền một đối số cho phương thức và sau đó chúng ta luôn gắn khối vào cuối lời gọi phương thức:

self.card :contacts do |c|
  // a chunk of valid ruby code    
end

Ok, vì vậy chúng tôi đang chuyển một đoạn mã cho một phương thức. Nhưng làm thế nào để chúng ta sử dụng khối này? Một tùy chọn là chuyển đổi đoạn mã thành một đối tượng. Ruby cung cấp ba cách để chuyển đổi một đoạn mã thành một đối tượng

# 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

Trong phương thức trên, & chuyển đổi khối được truyền cho phương thức thành một đối tượng và lưu trữ đối tượng đó trong khối biến cục bộ. Trên thực tế, chúng ta có thể chỉ ra rằng nó có hành vi tương tự như lambda và Proc.new:

def add(&block)
  block
end

l3 = add { |a| a + 1 }
l3.call(1)
=> 2

Điều này quan trọng. Khi bạn truyền một khối cho một phương thức và chuyển đổi nó bằng cách sử dụng &, đối tượng mà nó tạo ra sử dụng Proc.new để thực hiện chuyển đổi.

Lưu ý rằng tôi đã tránh sử dụng "Proc" làm tùy chọn. Đó là bởi vì Ruby 1.8, nó giống như lambda và trong Ruby 1.9, nó giống với Proc.new và trong tất cả các phiên bản Ruby nên tránh.

Vì vậy, sau đó bạn hỏi sự khác biệt giữa lambda và Proc.new là gì?

Đầu tiên, về mặt truyền tham số, lambda hoạt động giống như một cuộc gọi phương thức. Nó sẽ đưa ra một ngoại lệ nếu bạn vượt qua số lượng đối số sai. Ngược lại, Proc.new hành xử giống như sự phân công song song. Tất cả các đối số không sử dụng được chuyển đổi thành nil:

> 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 + 

Thứ hai, lambda và Proc.new xử lý từ khóa return khác nhau. Khi bạn thực hiện trả lại bên trong Proc.new, nó thực sự trở về từ phương thức kèm theo, đó là bối cảnh xung quanh. Khi bạn trở về từ khối lambda, nó chỉ trả về từ khối chứ không phải phương thức kèm theo. Về cơ bản, nó thoát khỏi lệnh gọi đến khối và tiếp tục thực hiện với phần còn lại của phương thức kèm theo.

> 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"

Vậy tại sao sự khác biệt hành vi này? Lý do là bởi với Proc.new, chúng ta có thể sử dụng các trình vòng lặp bên trong bối cảnh của các phương thức kèm theo và rút ra kết luận logic. Nhìn vào ví dụ này:

> 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

Chúng tôi hy vọng rằng khi chúng tôi gọi trở lại bên trong iterator, nó sẽ trở lại từ phương thức kèm theo. Hãy nhớ rằng các khối được truyền cho các trình vòng lặp được chuyển đổi thành các đối tượng bằng Proc.new và đó là lý do tại sao khi chúng ta sử dụng return, nó sẽ thoát khỏi phương thức kèm theo.

Bạn có thể nghĩ lambdas như các phương thức ẩn danh, chúng cô lập các khối mã riêng lẻ thành một đối tượng có thể được xử lý như một phương thức. Cuối cùng, hãy nghĩ về một lambda hành xử như một phương pháp vô cảm và Proc.new hành xử như mã nội tuyến.


0

Một bài viết hữu ích về hướng dẫn ruby: khối, procs & lambdas

Procs trở lại từ phương thức hiện tại, trong khi lambdas trở lại từ chính lambda.

Procs không quan tâm đến số lượng đối số chính xác, trong khi lambdas sẽ đưa ra một ngoại lệ.


-3

Sự khác biệt giữa Proc và lambda là Proc chỉ là một bản sao của mã với các đối số được thay thế lần lượt, trong khi lambda là một chức năng như trong các ngôn ngữ khác. (hành vi trả lại, kiểm tra đối số)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.