Và khi nào bạn sẽ sử dụng cái này chứ không phải cái kia?
Và khi nào bạn sẽ sử dụng cái này chứ không phải cái kia?
Câu trả lời:
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 {}
và 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 return
bên trong lambda sẽ trả về giá trị của lambda đó, nhưng sử dụng return
trong 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 return
câu lệnh để trả về giá trị của Proc, hãy sử dụng lambda
.
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
, retry
vv - 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 puts
trong 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, return
bê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
Chỉ có hai sự khác biệt chính.
lambda
kiểm tra số lượng đối số được truyền cho nó, trong khi a proc
không. Điều này có nghĩa là a lambda
sẽ đưa ra lỗi nếu bạn chuyển sai số lượng đối số, trong khi đó proc
sẽ bỏ qua các đối số không mong muốn và gán nil
cho bất kỳ đối số nào bị thiếu.lambda
trả về, nó chuyển điều khiển trở lại phương thức gọi; khi proc
trả 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 proc
nó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.
lambda
Tuy 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!"
# 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
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 Proc
như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 }
Đâ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.
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ệ.
return
câu lệnh trả về từproc
so vớilambda
.