Trong Ruby 1.8, một mặt có sự khác biệt tinh tế giữa Proc / lambda và Proc.new
mặt khác.
- Những khác biệt đó là gì?
- Bạn có thể đưa ra hướng dẫn về cách quyết định chọn cái nào không?
- Trong Ruby 1.9, Proc và lambda là khác nhau. Thỏa thuận là gì?
Trong Ruby 1.8, một mặt có sự khác biệt tinh tế giữa Proc / lambda và Proc.new
mặt khác.
Câu trả lời:
Một sự khác biệt quan trọng nhưng tinh tế khác giữa các procs được tạo với lambda
và procs được tạo ra Proc.new
là cách chúng xử lý return
câu lệnh:
lambda
Proc đã được xử lý, return
câu lệnh chỉ trả về từ chính ProcProc.new
Proc được điều trị, return
tuyên bố này đáng ngạc nhiên hơn một chút: nó trả lại quyền kiểm soát không chỉ từ Proc, mà còn từ phương thức kèm theo Proc!Đây là hành động lambda
của Proc return
. Nó hành xử theo cách mà bạn có thể mong đợi:
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"
Bây giờ đây là một Proc.new
Proc đã được return
điều trị làm điều tương tự. Bạn sắp được chứng kiến một trong những trường hợp mà Ruby phá vỡ Nguyên tắc tối thiểu được ca ngợi nhiều nhất:
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"
Nhờ hành vi đáng ngạc nhiên này (cũng như ít gõ), tôi có xu hướng thích sử dụng lambda
hơn Proc.new
khi tạo procs.
proc
phương pháp. Nó chỉ là một tốc ký cho Proc.new
?
proc
tương đương vớiProc.new
proc
hành động thích lambda
và không thích Proc.new
liên quan đến các tuyên bố trả về. Điều đó có nghĩa là tài liệu ruby không chính xác.
proc
hành động như lambda
trong 1.8, nhưng hành động như Proc.new
trong 1.9. Xem câu trả lời của Peter Wagenet.
lambda
là một phương thức ẩn danh. Vì nó là một phương thức, nó trả về một giá trị và phương thức gọi nó có thể làm với nó bất cứ điều gì nó muốn, bao gồm bỏ qua nó và trả về một giá trị khác. A Proc
giống như dán trong một đoạn mã. Nó không hoạt động như một phương pháp. Vì vậy, khi trả về xảy ra trong Proc
, đó chỉ là một phần của mã của phương thức được gọi là.
Để làm rõ thêm:
Joey nói rằng hành vi trở lại Proc.new
là đáng ngạc nhiên. Tuy nhiên, khi bạn cho rằng Proc.new hành xử như một khối thì điều này không có gì đáng ngạc nhiên vì đó chính xác là cách các khối hành xử. mặt khác lambas hành xử giống như phương pháp.
Điều này thực sự giải thích tại sao Procs linh hoạt khi nói đến arity (số lượng đối số) trong khi lambdas thì không. Các khối không yêu cầu tất cả các đối số của chúng được cung cấp nhưng các phương thức thì được (trừ khi được cung cấp mặc định). Mặc dù việc cung cấp mặc định đối số lambda không phải là một tùy chọn trong Ruby 1.8, nhưng hiện tại nó được hỗ trợ trong Ruby 1.9 với cú pháp lambda thay thế (như được lưu ý bởi webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Và Michiel de Mare (OP) không chính xác về Procs và lambda hành xử tương tự với arity trong Ruby 1.9. Tôi đã xác minh rằng họ vẫn duy trì hành vi từ 1.8 như đã nêu ở trên.
break
báo cáo không thực sự có ý nghĩa nhiều trong cả Procs hoặc lambdas. Trong Procs, giờ nghỉ sẽ đưa bạn trở lại từ Proc.new đã hoàn thành. Và nó không có ý nghĩa gì để thoát khỏi lambda vì về cơ bản nó là một phương pháp và bạn sẽ không bao giờ thoát khỏi cấp cao nhất của một phương thức.
next
, redo
và raise
hành xử giống nhau trong cả Procs và lambdas. Trong khi đó retry
không được phép trong một trong hai và sẽ đưa ra một ngoại lệ.
Và cuối cùng, proc
phương pháp không bao giờ nên được sử dụng vì nó không nhất quán và có hành vi bất ngờ. Trong Ruby 1.8, nó thực sự trả về một lambda! Trong Ruby 1.9, điều này đã được sửa và nó trả về một Proc. Nếu bạn muốn tạo một Proc, hãy gắn bó với Proc.new
.
Để biết thêm thông tin, tôi đánh giá cao Ngôn ngữ lập trình Ruby của O'Reilly là nguồn của tôi cho hầu hết các thông tin này.
break
từ tăng lương Procs LocalJumpError
, trong khi break
từ lambdas cư xử giống như return
( ví dụ , return nil
).
Tôi tìm thấy trang này cho thấy sự khác biệt giữa Proc.new
và lambda
là gì. Theo trang này, sự khác biệt duy nhất là lambda nghiêm ngặt về số lượng đối số mà nó chấp nhận, trong khi Proc.new
chuyển đổi các đối số bị thiếu thành nil
. Dưới đây là một ví dụ về phiên IRB minh họa sự khác biệt:
irb (chính): 001: 0> l = lambda {| x, y | x + y} => # <Proc: 0x00007fc605ec0748 @ (irb): 1> irb (chính): 002: 0> p = Proc.new {| x, y | x + y} => # <Proc: 0x00007fc605ea8698 @ (irb): 2> irb (chính): 003: 0> l.call "xin chào", "thế giới" => "hellowworld" irb (chính): 004: 0> p.call "xin chào", "thế giới" => "hellowworld" irb (chính): 005: 0> l.call "xin chào" ArgumentError: sai số lượng đối số (1 cho 2) từ (irb): 1 từ (irb): 5: trong 'cuộc gọi' từ (irb): 5 từ: 0 irb (chính): 006: 0> p.call "xin chào" TypeError: không thể chuyển đổi nil thành String từ (irb): 2: trong `+ ' từ (irb): 2 từ (irb): 6: trong 'cuộc gọi' từ (irb): 6 từ: 0
Trang này cũng khuyến nghị sử dụng lambda trừ khi bạn đặc biệt muốn hành vi chịu lỗi. Tôi đồng ý với tình cảm này. Sử dụng lambda có vẻ ngắn gọn hơn, và với sự khác biệt không đáng kể như vậy, có vẻ như sự lựa chọn tốt hơn trong tình huống trung bình.
Đối với Ruby 1.9, xin lỗi, tôi chưa tìm hiểu về 1.9, nhưng tôi không tưởng tượng họ sẽ thay đổi tất cả (mặc dù vậy, tôi không biết gì về nó, có vẻ như bạn đã nghe nói về một số thay đổi, vì vậy Tôi có lẽ sai ở đó).
Proc lớn tuổi hơn, nhưng ngữ nghĩa của sự trở lại rất phản trực giác đối với tôi (ít nhất là khi tôi đang học ngôn ngữ) bởi vì:
Lambda có chức năng an toàn hơn và dễ dàng hơn để lý do - Tôi luôn sử dụng nó thay vì Proc.
Tôi không thể nói nhiều về sự khác biệt tinh tế. Tuy nhiên, tôi có thể chỉ ra rằng Ruby 1.9 hiện cho phép các tham số tùy chọn cho lambdas và khối.
Đây là cú pháp mới cho lambdas stabby dưới 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 không có cú pháp đó. Cả cách khai báo khối / lambdas thông thường cũng không hỗ trợ các đối số tùy chọn:
# 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 }
Tuy nhiên, Ruby 1.9 hỗ trợ các đối số tùy chọn ngay cả với cú pháp cũ:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
Nếu bạn muốn xây dựng Ruby1.9 cho Leopard hoặc Linux, hãy xem bài viết này (tự quảng cáo không biết xấu hổ).
Câu trả lời ngắn: Điều quan trọng là cái gì return
: lambda tự trả về, và Proc trả về chính nó VÀ hàm gọi nó.
Điều ít rõ ràng hơn là tại sao bạn muốn sử dụng mỗi. lambda là những gì chúng ta mong đợi mọi thứ nên làm trong một ý nghĩa lập trình chức năng. Về cơ bản, nó là một phương thức ẩn danh với phạm vi hiện tại tự động bị ràng buộc. Trong hai, lambda là một trong những bạn có thể nên sử dụng.
Proc, mặt khác, thực sự hữu ích cho việc thực hiện ngôn ngữ. Ví dụ: bạn có thể thực hiện các câu lệnh "if" hoặc "for" với chúng. Bất kỳ trả về nào được tìm thấy trong Proc sẽ trả về phương thức đã gọi nó, không chỉ là câu lệnh "if". Đây là cách ngôn ngữ hoạt động, cách các câu lệnh "nếu" hoạt động, vì vậy tôi đoán là Ruby sử dụng điều này dưới vỏ bọc và họ chỉ phơi bày nó vì nó có vẻ mạnh mẽ.
Bạn sẽ chỉ thực sự cần điều này nếu bạn đang tạo các cấu trúc ngôn ngữ mới như các vòng lặp, các cấu trúc if-other, v.v.
Tôi không nhận thấy bất kỳ bình luận nào về phương pháp thứ ba trong nhiệm vụ, "Proc" không được dùng nữa, nhưng được xử lý khác nhau trong 1.8 và 1.9.
Đây là một ví dụ khá dài dòng giúp bạn dễ dàng thấy sự khác biệt giữa ba cuộc gọi tương tự:
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
trả lại một lambda trong 1,8; hiện đã được sửa chữa để trả lại một Proc trong 1.9 - tuy nhiên đây là một thay đổi đột phá; do đó không nên sử dụng nữa
Closures in Ruby là một tổng quan tốt về cách các khối, lambda và Proc hoạt động trong Ruby, với Ruby.
lambda hoạt động như mong đợi, giống như trong các ngôn ngữ khác.
Các dây Proc.new
là đáng ngạc nhiên và khó hiểu.
Câu return
lệnh trong Proc được tạo bởi Proc.new
sẽ không chỉ trả về điều khiển từ chính nó, mà còn từ phương thức kèm theo nó .
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Bạn có thể lập luận rằng Proc.new
chèn mã vào phương thức kèm theo, giống như khối. Nhưng Proc.new
tạo ra một đối tượng, trong khi khối là một phần của đối tượng.
Và có một sự khác biệt khác giữa lambda và Proc.new
, đó là cách xử lý (sai) của họ. lambda phàn nàn về nó, trong khi Proc.new
bỏ qua các đối số bổ sung hoặc coi sự vắng mặt của các đối số là con số không.
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
trong Ruby 1.8 tạo ra lambda, trong khi trong Ruby 1.9+ hoạt động như thế Proc.new
, điều này thực sự khó hiểu.
Để giải thích về phản ứng của Accordion Guy:
Lưu ý rằng Proc.new
tạo ra một Proc bằng cách thông qua một khối. Tôi tin rằng nó lambda {...}
được phân tích cú pháp như một loại nghĩa đen, chứ không phải là một cuộc gọi phương thức vượt qua một khối. return
ing từ bên trong một khối gắn liền với một cuộc gọi phương thức sẽ trả về từ phương thức, không phải khối vàProc.new
trường hợp này là một ví dụ về điều này khi chơi.
(Đây là 1.8. Tôi không biết cách dịch này thành 1.9.)
Tôi hơi muộn về điều này, nhưng có một điều tuyệt vời nhưng ít được biết đến về việc Proc.new
không được đề cập trong các bình luận. Theo tài liệu :
Proc::new
có thể được gọi mà không có khối chỉ trong một phương thức có khối đính kèm, trong trường hợp đó khốiProc
đó được chuyển đổi thành đối tượng.
Điều đó nói rằng, Proc.new
cho phép chuỗi phương pháp năng suất:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&block
đối số trong def
, nhưng không phải làm điều đó trong danh sách def arg.
Cần nhấn mạnh rằng return
trong một Proc trả về từ phương thức bao quanh từ vựng, tức là phương thức mà Proc được tạo ra , không phải là phương thức được gọi là Proc. Đây là một hệ quả của tài sản đóng cửa của procs. Vì vậy, đoạn mã sau không có kết quả gì:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
Mặc dù Proc thực thi trong foobar
, nó đã được tạo ra foo
và vì vậy các return
lối thoát hiểm foo
, không chỉ foobar
. Như Charles Caldwell đã viết ở trên, nó có cảm giác GOTO với nó. Theo tôi, return
sẽ ổn trong một khối được thực thi trong ngữ cảnh từ vựng của nó, nhưng ít trực quan hơn khi được sử dụng trong một Proc được thực thi trong một ngữ cảnh khác.
Sự khác biệt trong hành vi với return
IMHO là sự khác biệt quan trọng nhất giữa 2. Tôi cũng thích lambda vì nó ít gõ hơn Proc.new :-)
proc {}
. Tôi không chắc khi điều này có hiệu lực, nhưng nó (hơi) dễ hơn là phải gõ Proc.new.