Khi nào nên sử dụng lambda, khi nào nên sử dụng Proc.new?


336

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ì?

3
Xem thêm: cuốn sách Ngôn ngữ lập trình Ruby của Matz và Flanagan, nó đã bao quát toàn diện chủ đề này. Proc hành xử giống như một khối - ngữ nghĩa năng suất, trong đó như lambda hành xử giống như một phương thức - ngữ nghĩa gọi phương thức. Cũng trở lại, phá vỡ, et. tất cả cư xử khác biệt trong procs n lambdas
Gishu

1
Đồng thời xem bài đăng chi tiết về sự khác biệt
Akshay Rawat

bạn đã chấp nhận câu trả lời chỉ nói lên sự khác biệt giữa Proc và lambda, trong khi tiêu đề của câu hỏi của bạn là khi nào nên sử dụng những thứ đó
Shri

Câu trả lời:


378

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:

  • Trong một lambdaProc đã được xử lý, returncâu lệnh chỉ trả về từ chính Proc
  • Trong một Proc.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.


12
Rồi cũng có procphương pháp. Nó chỉ là một tốc ký cho Proc.new?
panzi


4
@mattdipasquale Trong các thử nghiệm của tôi, 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.
Kelvin

31
@mattdipasquale Xin lỗi tôi chỉ đúng một nửa. 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.
Kelvin

55
Tại sao hành vi "đáng ngạc nhiên" này? A 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à.
Arcolye

96

Để 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, redoraisehà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.


1
"" "Tuy nhiên, khi bạn cho rằng Proc.new hành xử giống 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 hoạt động." "" <- khối là một phần của một đối tượng, trong khi Proc.new tạo ra một đối tượng. Cả lambda và Proc.new đều tạo ra một đối tượng có lớp là Proc, tại sao lại khác?
suy yếu

1
Như của Ruby 2.5, breaktừ tăng lương Procs LocalJumpError, trong khi breaktừ lambdas cư xử giống như return( ví dụ , return nil).
Masa Sakano

43

Tôi tìm thấy trang này cho thấy sự khác biệt giữa Proc.newlambdalà 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 ở đó).


2
procs cũng trở lại khác với lambdas.
Cam

"" "Proc.new chuyển đổi các đối số bị thiếu thành nil" "" Proc.new cũng bỏ qua các đối số bổ sung (tất nhiên lambda phàn nàn điều này có lỗi).
suy yếu

16

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ì:

  1. Nếu bạn đang sử dụng Proc, rất có thể bạn đang sử dụng một số mô hình chức năng.
  2. Proc có thể quay trở lại phạm vi kèm theo (xem các phản hồi trước đó), về cơ bản là một goto và không có chức năng cao.

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.


11

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ác thông số tùy chọn trong lambda rất cần thiết, tôi rất vui vì họ đã thêm nó vào 1.9. Tôi giả sử các khối cũng có thể có các tham số tùy chọn sau đó (trong 1.9)?
mpd

bạn không thể hiện các tham số mặc định trong các khối, chỉ lambdas
iconoclast

11

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.


1
"Lambda trả lại cho chính nó, và Proc trả về chính nó VÀ chức năng gọi nó" là sai hoàn toàn và một sự hiểu lầm rất phổ biến. Một Proc là một bao đóng và trả về từ phương thức đã tạo ra nó. Xem câu trả lời đầy đủ của tôi ở nơi khác trên trang.
ComDubh

10

Một cách tốt để thấy rằng lambdas được thực thi trong phạm vi riêng của chúng (như thể đó là một cuộc gọi phương thức), trong khi Procs có thể được xem là thực thi nội tuyến với phương thức gọi, ít nhất đó là một cách tốt để quyết định sử dụng phương thức nào trong mỗi trường hợp.


8

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

1
Matz đã tuyên bố rằng ông dự định sẽ từ chối vì điều đó thật khó hiểu khi Proc và Proc.new trả lại kết quả khác nhau. Trong 1.9, họ hành xử giống nhau mặc dù (Proc là bí danh của Proc.new). eigenclass.org/hiki/Thanges+in+Ruby+1.9#l47
Dave Rapin

@banister: 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
Gishu

Tôi nghĩ rằng Pickaxe nói trong một chú thích ở đâu đó rằng Proc được mô tả một cách hiệu quả hoặc một cái gì đó. Tôi không có số trang chính xác.
dertoni

7

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.


Tôi đã ngừng đọc điều này sau khi tôi đọc "một chức năng không thể chấp nhận nhiều khối - vi phạm nguyên tắc rằng các bao đóng có thể được truyền qua một cách tự do như các giá trị." Khối không đóng cửa. Procs là, và một chức năng có thể chấp nhận nhiều procs.
ComDubh

5

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.


3

Để 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.)


3

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!

Thật thú vị, nó làm điều tương tự như khai báo một &blockđối số trong def, nhưng không phải làm điều đó trong danh sách def arg.
jrochkind

2

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.


1

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 :-)


2
Để cập nhật: procs bây giờ có thể được tạo bằng cách sử dụng 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.
aceofbassgreg
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.