Lambda ne zaman kullanılır, Proc.new ne zaman kullanılır?


336

Ruby 1.8'de bir yandan proc / lambda ve Proc.newdiğer yandan ince farklılıklar vardır .

  • Bu farklar neler?
  • Hangisini seçeceğine nasıl karar vereceğine dair rehberlik verebilir misin?
  • Ruby 1.9'da proc ve lambda farklıdır. Anlaşma ne?

3
Ayrıca bakınız: Matz ve Flanagan'ın Ruby Programlama Dili kitabı, bu konuyu kapsamlı bir şekilde ele aldı. proc, blok - verim semantiği gibi davranır; lambda ise yöntem - yöntem çağrısı semantiği gibi davranır. Ayrıca geri dön, kır, et. tüm procs n lambdas farklı davranır
Gishu


sadece proc ve lambda arasındaki farkın ne olduğunu söyleyen yanıtı alırken, sorunuzun başlığı bu şeyleri ne zaman kullanacağınız
Shri

Yanıtlar:


378

İle oluşturulan procs arasındaki bir diğer önemli ancak ince bir fark lambdaile oluşturulan ve procs Proc.newHalledemeyecekleri nasıl returndeyimi:

  • Oluşturulan bir lambdaişlemde, returnifade yalnızca işlemin kendisinden döner
  • Oluşturulan bir Proc.newproc'ta, returnifade biraz daha şaşırtıcıdır: sadece proc'tan değil, aynı zamanda proc'u kapsayan yöntemden de kontrol döndürür !

İşte - oluşturulan lambdaproclar iş başında return. Muhtemelen beklediğiniz şekilde davranır:

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"

Şimdi, burada Proc.newyaratılmış bir proc returnaynı şeyi yapıyor. Ruby'nin çok şaşkın En Az Sürpriz İlkesini ihlal ettiği durumlardan birini görmek üzeresiniz:

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"

Bu şaşırtıcı davranış (yanı sıra daha az yazarak) sayesinde kullanıyorum lehine eğilimindedir lambdaüzerinde Proc.newprocs yaparken.


12
Sonra procyöntem de var . Sadece bir stenografi Proc.newmi?
Kasım'da panzi


4
@mattdipasquale Testlerimde, iade ifadeleriyle ilgili ve beğenmediği gibi procdavranıyor . Bu, yakut dokümanın yanlış olduğu anlamına gelir. lambdaProc.new
Kelvin

31
@mattdipasquale Üzgünüm sadece yarı haklıydım. 1.8 procgibi davranır lambda, ancak Proc.new1.9 gibi davranır . Peter Wagenet'in cevabına bakınız.
Kelvin

55
Neden bu "şaşırtıcı" davranış? A lambdaanonim bir yöntemdir. Bir yöntem olduğundan, bir değer döndürür ve onu çağıran yöntem, onu görmezden gelmek ve farklı bir değer döndürmek de dahil olmak üzere, ne isterse yapabilir. A Proc, bir kod pasajına yapıştırma gibidir. Bir yöntem gibi davranmaz. Yani içinde bir dönüş gerçekleştiğinde Proc, bu, onu çağıran yöntemin kodunun sadece bir parçasıdır.
Arcolye

96

Daha fazla açıklama yapmak için:

Joey, geri dönüş davranışının Proc.newşaşırtıcı olduğunu söylüyor . Ancak Proc.new'in bir blok gibi davrandığını düşündüğünüzde, bu tam olarak blokların nasıl davrandığı gibi şaşırtıcı değildir. Diğer taraftan lambalar daha çok benzer yöntemlere sahiptir.

Bu aslında Procs'un arity (argüman sayısı) konusunda neden esnek olduğunu, ancak lambdaların olmadığını açıklar. Bloklar, tüm argümanlarının verilmesini gerektirmez, ancak yöntemler gerektirir (bir varsayılan belirtilmediği sürece). Lambda argümanı varsayılanı Ruby 1.8'de bir seçenek olmasa da, şimdi Ruby 1.9'da alternatif lambda sözdizimi ile desteklenmektedir (webmat tarafından belirtildiği gibi):

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

Ve Michiel de Mare (OP) Procs ve lambda'nın Ruby 1.9'da aynı şekilde davranması konusunda yanlıştır. Yukarıda belirtildiği gibi 1.8'den hala davranışlarını koruduklarını doğruladım.

breakProcs veya lambdalarda ifadelerin pek bir anlamı yoktur. Procs'ta, mola sizi zaten tamamlanmış olan Proc.new'ten döndürür. Ve esasen bir yöntem olduğu için lambdadan kopmak mantıklı değil ve asla bir yöntemin en üst seviyesinden asla kopmayacaksınız.

next, redove raiseProcs ve lambdalarda da aynı şekilde davranır. Oysa retryikisinde de izin verilmez ve istisna oluşturur.

Ve son olarak, procyöntem asla tutarsız olduğu ve beklenmedik davranışları olduğu için kullanılmamalıdır. Ruby 1.8'de aslında bir lambda döndürür! Ruby 1.9'da bu düzeltildi ve bir Proc döndürüyor. Bir Proc oluşturmak istiyorsanız, ile devam edin Proc.new.

Daha fazla bilgi için, bu bilgilerin çoğunda kaynağım olan O'Reilly'nin Ruby Programlama Dili'ni şiddetle tavsiye ederim .


1
"" "Ancak Proc.new'in bir blok gibi davrandığını düşündüğünüzde, bu tam olarak blokların nasıl davrandığı şaşırtıcı değildir." "" "- Proc.new bir nesne oluştururken. Hem lambda hem de Proc.new, sınıfı Proc olan bir nesne oluşturur, neden fark edilir?
14'te zayıf

1
Ruby 2.5 itibariyle breakyordamlara yükseltme gelen LocalJumpError, oysa breaklambda'lar boğulan bir mesafede gibi return( yani , return nil).
Masa Sakano

43

Bulduğum bu sayfayı arasındaki gösterileri ne fark Proc.newve lambdavardır. Sayfaya göre, tek fark bir lambda'nın kabul ettiği argüman sayısı konusunda katı olması, Proc.neweksik argümanların dönüştürülmesidir nil. İşte farkı gösteren örnek bir IRB oturumu:

irb (ana): 001: 0> l = lambda {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (ana): 002: 0> p = Proc.new {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (main): 003: 0> l.call "merhaba", "dünya"
=> "helloworld"
irb (main): 004: 0> p.call "merhaba", "dünya"
=> "helloworld"
irb (main): 005: 0> l.call "merhaba"
ArgumentError: yanlış sayıda argüman (2 için 1)
    itibaren (irb): 1
    from (irb): 5: `` çağrıda ''
    itibaren (irb): 5
    from: 0
irb (main): 006: 0> p.call "merhaba"
TypeError: nil'i String'e dönüştüremiyor
    from (irb): 2: `` + '' da
    itibaren (irb): 2
    from (irb): 6: `` çağrıda ''
    itibaren (irb): 6
    from: 0

Hataya dayanıklı davranışı özellikle istemediğiniz sürece, sayfa lambda kullanmanızı da önerir. Bu düşünceye katılıyorum. Bir lambda kullanmak biraz daha özlü görünüyor ve bu kadar önemsiz bir farkla, ortalama durumda daha iyi bir seçim gibi görünüyor.

Ruby 1.9'a gelince, özür dilerim, henüz 1.9'a bakmadım, ama bunu çok fazla değiştireceklerini düşünmüyorum (bunun için sözümü almayın, bazı değişiklikler duymuşsunuz gibi görünüyor, bu yüzden Muhtemelen orada yanılıyorum).


2
procs da lambdalardan farklı bir şekilde geri dönüyor.
Cam

"" "Proc.new eksik argümanları nil'e dönüştürür" "" Proc.new ayrıca fazladan argümanları da yok sayar (elbette lambda bunu bir hata ile şikayet eder).
16'da zayıf

16

Proc daha eskidir, ancak dönüş semantiği bana karşı oldukça mantıklı değildir (en azından dili öğrenirken):

  1. Proc kullanıyorsanız, büyük olasılıkla bir çeşit işlevsel paradigma kullanıyorsunuzdur.
  2. Proc, temel olarak bir goto olan ve son derece işlevsel olmayan doğası olan kapalı kapsamdan çıkabilir (önceki yanıtlara bakın).

Lambda işlevsel olarak daha güvenli ve akıl yürütmesi daha kolay - her zaman proc yerine kullanıyorum.


11

İnce farklar hakkında fazla bir şey söyleyemem. Ancak, Ruby 1.9'un artık lambdalar ve bloklar için isteğe bağlı parametrelere izin verdiğini belirtebilirim.

İşte 1.9'ın altındaki stabby lambdas için yeni sözdizimi:

stabby = ->(msg='inside the stabby lambda') { puts msg }

Ruby 1.8'in sözdizimi yoktu. Blokları / lambdaları bildirmenin geleneksel yolu da isteğe bağlı argümanları desteklemedi:

# 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 }

Ancak Ruby 1.9, eski sözdiziminde bile isteğe bağlı bağımsız değişkenleri destekler:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

Leopard veya Linux için Ruby1.9 oluşturmak istiyorsanız, bu makaleye göz atın (utanmaz öz tanıtım).


Lambda içindeki isteğe bağlı parametreler çok gerekliydi, 1.9'da eklediklerine sevindim. Ben bloklar da o zaman da isteğe bağlı parametreleri olabilir varsayalım (1.9)?
mpd

bloklarda varsayılan parametreleri göstermiyorsunuz, sadece lambdalar
iconoclast

11

Kısa cevap: Önemli olan nedir return: lambda kendi dışına döner ve proc kendi dışına döner ve onu çağıran işlev.

Daha az net olan, neden her birini kullanmak istediğinizdir. lambda, işlevsel bir programlama anlamında işlerin yapmasını beklediğimiz şeydir. Temelde mevcut kapsam otomatik olarak bağlanan anonim bir yöntemdir. İkisinden lambda muhtemelen kullanmalısınız.

Öte yandan Proc, dilin kendisini uygulamak için gerçekten yararlıdır. Örneğin, "if" ifadelerini veya "for" döngülerini onlarla birlikte uygulayabilirsiniz. Proc'ta bulunan herhangi bir dönüş, yalnızca "if" ifadesini değil, onu çağıran yöntemden geri döner. Diller bu şekilde çalışır, "if" ifadeleri böyle çalışır, bu yüzden tahminim Ruby bunu kapakların altında kullanıyor ve güçlü görünüyordu.

Buna sadece döngüler, if-else yapıları gibi yeni dil yapıları oluşturuyorsanız gerçekten ihtiyacınız olacak.


1
"lambda kendi dışına döner ve proc kendi dışına geri döner VE onu çağıran işlev" oldukça yanlıştır ve çok yaygın bir yanlış anlamadır. Proc bir kapanıştır ve onu oluşturan yöntemden döner. Cevabımı sayfanın başka bir yerinde görebilirsiniz.
ComDubh

10

Bunu görmenin iyi bir yolu, lambdasların kendi kapsamlarında yürütüldüğü (bir yöntem çağrısı gibi), Procs'un çağrı yöntemiyle satır içi yürütülmüş olarak görüntülenebilmesidir, en azından bu, hangisinin kullanılacağına karar vermenin iyi bir yoludur. herbir durumda.


8

Queston'da "proc" un kullanımdan kaldırıldığı ancak 1.8 ve 1.9'da farklı şekilde ele alındığı üçüncü yöntemle ilgili herhangi bir yorum fark etmedim.

Üç benzer çağrı arasındaki farkları görmeyi kolaylaştıran oldukça ayrıntılı bir örnek:

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, proc ve Proc.new'in farklı sonuçlar döndürmesi kafa karıştırıcı olduğu için onu reddetmeyi planladığını belirtti. 1.9'da da aynı şekilde davranırlar (proc Proc.new için bir takma addır). ejenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin

@banister: 1.8'de procbir lambda döndürdü; şimdi bir proc'u 1.9'da iade etmek için düzeltildi - ancak bu bir kırılma değişikliği; bu nedenle artık kullanılması tavsiye edilmez
Gishu

Bence kazma, bir yerde dipnotta proc'un etkili bir şekilde sınır dışı edildiğini söylüyor. Tam sayfa numaram yok.
dertoni

7

Ruby'deki kapanışlar Ruby'de bloklar, lambda ve proc'un Ruby ile nasıl çalıştığı hakkında iyi bir genel bakış.


"Bir fonksiyon birden fazla bloğu kabul edemez - kapanışların değer olarak serbestçe iletilebileceği ilkesini ihlal ederek" yazdıktan sonra bunu okumayı bıraktım. Bloklar kapak değildir. Proc'lardır ve bir işlev birden çok proc'u kabul edebilir.
ComDubh

5

lambda diğer dillerde olduğu gibi beklendiği gibi çalışır.

Kablolu Proc.new şaşırtıcı ve kafa karıştırıcı.

returnYarattığı proc deyimi Proc.newsadece kendisinden kontrolünü dönmek olmaz, ama bunu çevreleyen yönteminden de .

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Proc.newKodun, tıpkı blok gibi, kapatma yöntemine eklendiğini iddia edebilirsiniz . Ama Proc.newolan blok ise, bir nesneyi yaratır parçası bir nesnenin .

Ve lambda ile Proc.new, (yanlış) argümanları ele almaları arasında başka bir fark var . lambda bundan şikayetçi olurken, Proc.newekstra argümanları göz ardı eder veya argümanların yokluğunu sıfır olarak kabul eder.

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, procRuby 1.8'de bir lambda yaratırken, Ruby 1.9 + 'da böyle davranır Proc.newve bu gerçekten kafa karıştırıcıdır.


3

Akordeon Guy'ın cevabını detaylandırmak için:

Proc.newBir blok geçirilerek bir proc çıkışı oluşturan dikkat edin . Ben lambda {...}bir blok geçen bir yöntem çağrısı yerine, bir tür değişmez olarak ayrıştırıldığını düşünüyorum . returnbir yöntem çağrısına eklenmiş bir bloğun içinden girilmesi, bloktan değil, yöntemden dönerProc.new durum, buna örnek olarak gösterilebilir.

(Bu 1.8. Bunun 1.9'a nasıl dönüştüğünü bilmiyorum.)


3

Bu konuda biraz geç kaldım, ama Proc.newyorumlarda hiç bahsedilmeyen büyük ama az bilinen bir şey var . Tarafından gibi belgeler :

Proc::newyalnızca ekli bloğa sahip bir yöntem içinde bir blok olmadan çağrılabilir, bu durumda bu blokProc nesneye dönüştürülür .

Bununla birlikte, Proc.newverim yöntemlerini zincirleyelim:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

İlginç bir şekilde, arg &blockargümanında bir argüman bildirmekle aynı şeyi yapar def, ancak def arg listesinde bunu yapmak zorunda kalmadan.
jrochkind

2

Vurgulayan It değerinde olduğunu returnlexically çevreleyen yönteminden bir proc iadeler, yani içinde proc oluşturulduğu yönteme , değil proc denilen yöntem. Bu, prokilerin kapanma özelliğinin bir sonucudur. Yani aşağıdaki kod hiçbir şey çıktı:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Proc yürütülürken , sadece değil, aynı zamanda çıkışlar foobarda yaratıldı . Charles Caldwell'in yukarıda yazdığı gibi, bir GOTO hissi var. Bence, sözcüksel bağlamında yürütülen bir blokta iyidir, ancak farklı bir bağlamda yürütülen bir proc'ta kullanıldığında çok daha az sezgiseldir.fooreturnfoofoobarreturn


1

returnİle davranış farkı IMHO 2 arasında en önemli fark. Ben de lambda tercih ederim çünkü Proc.new daha az yazarak :-)


2
Güncellemek için: şimdi procs kullanılarak oluşturulabilir proc {}. Bunun ne zaman yürürlüğe girdiğinden emin değilim, ancak Proc.new yazmak zorunda kalmaktan (biraz) daha kolay.
aceofbassgreg
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.