Trong Ruby 1.8, một mặt có sự khác biệt tinh tế giữa Proc / lambda và Proc.newmặ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.newmặ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 lambdavà procs được tạo ra Proc.newlà cách chúng xử lý returncâu lệnh:
lambdaProc đã được xử lý, returncâu lệnh chỉ trả về từ chính ProcProc.newProc được điều trị, returntuyê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 lambdacủ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.newProc đã đượ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 lambdahơn Proc.newkhi tạo procs.
procphương pháp. Nó chỉ là một tốc ký cho Proc.new?
proctương đương vớiProc.new
prochành động thích lambdavà không thích Proc.newliê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.
prochành động như lambdatrong 1.8, nhưng hành động như Proc.newtrong 1.9. Xem câu trả lời của Peter Wagenet.
lambdalà 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 Procgiố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.newlà đá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.
breakbá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, redovà raisehành xử giống nhau trong cả Procs và lambdas. Trong khi đó retrykhông được phép trong một trong hai và sẽ đưa ra một ngoại lệ.
Và cuối cùng, procphươ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.
breaktừ tăng lương Procs LocalJumpError, trong khi breaktừ 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.newvà lambdalà 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.newchuyể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
proctrả 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.newlà đáng ngạc nhiên và khó hiểu.
Câu returnlệnh trong Proc được tạo bởi Proc.newsẽ 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.newchèn mã vào phương thức kèm theo, giống như khối. Nhưng Proc.newtạ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.newbỏ 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, proctrong 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.newtạ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. returning 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.newkhông được đề cập trong các bình luận. Theo tài liệu :
Proc::newcó 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.newcho 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 returntrong 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 foovà vì vậy các returnlố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, returnsẽ ổ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 returnIMHO 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.