Wann sollte Lambda verwendet werden, wann sollte Proc.new verwendet werden?

336

In Ruby 1.8 gibt es subtile Unterschiede zwischen proc / lambda einerseits und Proc.newandererseits.

  • Was sind diese Unterschiede?
  • Können Sie Richtlinien geben, wie Sie entscheiden sollen, welche Sie wählen möchten?
  • In Ruby 1.9 unterscheiden sich proc und lambda. Was ist das Problem?
Michiel de Mare
quelle
3
Siehe auch: Das Ruby Programming Language-Buch von Matz und Flanagan hat dieses Thema umfassend behandelt. proc verhält sich wie eine Block-Yield-Semantik, während sich Lambda wie eine Methode-Methode-Aufrufsemantik verhält. Auch zurückkehren, brechen, et. Alle verhalten sich unterschiedlich in procs n lambdas
Gishu
1
Siehe auch einen ausführlichen Beitrag über Kontrollflussunterschiede zwischen Ruby Procs und Lambdas
Akshay Rawat
Sie haben die Antwort akzeptiert, die nur sagt, was der Unterschied zwischen proc und lambda ist, während der Titel Ihrer Frage lautet, wann Sie diese Dinge verwenden sollen
Shri

Antworten:

378

Ein weiterer wichtiger, aber subtiler Unterschied zwischen Procs, die mit lambdaund Procs erstellt wurden, Proc.newbesteht darin, wie sie mit der returnAussage umgehen :

  • In einem erstellten lambdaProzess wird die returnAnweisung nur vom Prozess selbst zurückgegeben
  • In einem erstellten Proc.newProc ist die returnAussage etwas überraschender: Sie gibt die Kontrolle nicht nur vom Proc zurück, sondern auch von der Methode, die den Proc einschließt!

Hier sind erstellte lambdaProcs returnin Aktion. Es verhält sich so, wie Sie es wahrscheinlich erwarten:

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"

Hier ist ein erstellter Proc.newProc, der returndas Gleiche tut. Sie werden einen dieser Fälle sehen, in denen Ruby das viel gepriesene Prinzip der geringsten Überraschung bricht:

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"

Dank dieses überraschenden Verhalten (wie auch weniger Typisierung), neige ich dazu , mit zu begünstigen lambdaüber , Proc.newwenn Procs zu machen.

Joey deVilla
quelle
12
Dann gibt es noch die procMethode. Ist es nur eine Abkürzung für Proc.new?
Panzi
6
@panzi, ja, procist gleichbedeutend mitProc.new
ma11hew28
4
@mattdipasquale In meinen Tests verhält es sich in Bezug auf Rückgabeanweisungen procwie lambdaund nicht wie Proc.new. Das heißt, das Ruby-Dokument ist ungenau.
Kelvin
31
@mattdipasquale Sorry, ich hatte nur halb recht. procverhält sich wie lambdain 1.8, verhält sich aber wie Proc.newin 1.9. Siehe die Antwort von Peter Wagenet.
Kelvin
55
Warum ist dieses "überraschende" Verhalten? A lambdaist eine anonyme Methode. Da es sich um eine Methode handelt, gibt sie einen Wert zurück, und die Methode, die sie aufgerufen hat, kann damit tun, was sie will, einschließlich Ignorieren und Zurückgeben eines anderen Werts. A Procist wie das Einfügen eines Code-Snippets. Es verhält sich nicht wie eine Methode. Wenn also eine Rückgabe innerhalb von Procerfolgt, ist dies nur ein Teil des Codes der Methode, die sie aufgerufen hat.
Arcolye
96

Zur weiteren Klärung:

Joey sagt, dass das Rückkehrverhalten von Proc.new überraschend ist. Wenn Sie jedoch bedenken, dass sich Proc.new wie ein Block verhält, ist dies nicht überraschend, da sich Blöcke genau so verhalten. Lambas hingegen verhalten sich eher wie Methoden.

Dies erklärt tatsächlich, warum Procs flexibel sind, wenn es um Arität (Anzahl der Argumente) geht, Lambdas jedoch nicht. Für Blöcke müssen nicht alle Argumente angegeben werden, für Methoden jedoch (sofern kein Standardwert angegeben ist). Während das Bereitstellen der Lambda-Argument-Standardeinstellung in Ruby 1.8 keine Option ist, wird sie jetzt in Ruby 1.9 mit der alternativen Lambda-Syntax (wie von webmat angegeben) unterstützt:

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

Und Michiel de Mare (das OP) ist falsch darüber, dass sich Procs und Lambda in Ruby 1.9 mit Arity gleich verhalten. Ich habe überprüft, dass sie das oben angegebene Verhalten von 1.8 beibehalten.

breakAussagen sind weder in Procs noch in Lambdas sinnvoll. In Procs würde die Pause Sie von Proc.new zurückbringen, das bereits abgeschlossen wurde. Und es macht keinen Sinn, von einem Lambda abzubrechen, da es im Wesentlichen eine Methode ist und Sie niemals von der obersten Ebene einer Methode abbrechen würden.

next, redoUnd raiseverhalten sich die in beiden Procs und Lambdas. Wobei auch retrynicht erlaubt ist und eine Ausnahme auslöst.

Und schließlich sollte die procMethode niemals verwendet werden, da sie inkonsistent ist und unerwartetes Verhalten aufweist. In Ruby 1.8 wird tatsächlich ein Lambda zurückgegeben! In Ruby 1.9 wurde dies behoben und es wird ein Proc zurückgegeben. Wenn Sie einen Proc erstellen möchten, bleiben Sie bei Proc.new.

Für weitere Informationen empfehle ich O'Reillys The Ruby Programming Language, die meine Quelle für die meisten dieser Informationen ist.

Peter Wagenet
quelle
1
"" Wenn Sie jedoch bedenken, dass sich Proc.new wie ein Block verhält, ist dies nicht überraschend, da sich Blöcke genau so verhalten. "" <- Block ist Teil eines Objekts, während Proc.new ein Objekt erstellt. Sowohl Lambda als auch Proc.new erstellen ein Objekt, dessen Klasse Proc ist. Warum diff?
schwächer
1
Wie von Ruby 2.5, breakvon Procs wirft LocalJumpError, während breakvon Lambdas verhält sich genauso wie return( dh , return nil).
Masa Sakano
43

Ich habe diese Seite gefunden, die zeigt, was der Unterschied zwischen Proc.newund lambdaist. Laut der Seite besteht der einzige Unterschied darin, dass ein Lambda hinsichtlich der Anzahl der akzeptierten Argumente streng ist, während Proc.newfehlende Argumente in konvertiert werden nil. Hier ist eine IRB-Beispielsitzung, die den Unterschied veranschaulicht:

irb (main): 001: 0> l = Lambda {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (main): 002: 0> p = Proc.new {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (main): 003: 0> l.call "hallo", "world"
=> "helloworld"
irb (main): 004: 0> p.call "hallo", "world"
=> "helloworld"
irb (main): 005: 0> l.call "hallo"
ArgumentError: falsche Anzahl von Argumenten (1 für 2)
    von (irb): 1
    von (irb): 5: in `call '
    von (irb): 5
    von: 0
irb (main): 006: 0> p.call "hallo"
TypeError: nil kann nicht in String konvertiert werden
    von (irb): 2: in `+ '
    von (irb): 2
    von (irb): 6: in `call '
    von (irb): 6
    von: 0

Auf dieser Seite wird außerdem die Verwendung von Lambda empfohlen, es sei denn, Sie möchten ausdrücklich das fehlertolerante Verhalten. Ich stimme diesem Gefühl zu. Die Verwendung eines Lambda scheint etwas prägnanter zu sein, und mit einem so unbedeutenden Unterschied scheint es in der durchschnittlichen Situation die bessere Wahl zu sein.

Was Ruby 1.9 betrifft, tut mir leid, ich habe mich noch nicht mit 1.9 befasst, aber ich kann mir nicht vorstellen, dass sie das alles so sehr ändern würden (nehmen Sie mein Wort nicht dafür, es scheint, als hätten Sie von einigen Änderungen gehört, also Ich liege dort wahrscheinlich falsch.

Mike Stone
quelle
2
Procs kehren auch anders zurück als Lambdas.
Cam
"" "Proc.new konvertiert fehlende Argumente in" "". Proc.new ignoriert auch zusätzliche Argumente (natürlich beschwert sich Lambda mit einem Fehler).
schwächer
16

Proc ist älter, aber die Semantik der Rückkehr ist für mich sehr uninteressant (zumindest als ich die Sprache lernte), weil:

  1. Wenn Sie proc verwenden, verwenden Sie höchstwahrscheinlich eine Art funktionales Paradigma.
  2. Proc kann aus dem umschließenden Bereich zurückkehren (siehe vorherige Antworten), was im Grunde genommen ein Goto ist und von Natur aus höchst funktionsunfähig ist.

Lambda ist funktional sicherer und leichter zu überlegen - ich benutze es immer anstelle von proc.

Charles Caldwell
quelle
11

Ich kann nicht viel über die subtilen Unterschiede sagen. Ich kann jedoch darauf hinweisen, dass Ruby 1.9 jetzt optionale Parameter für Lambdas und Blöcke zulässt.

Hier ist die neue Syntax für die Stabby Lambdas unter 1.9:

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

Ruby 1.8 hatte diese Syntax nicht. Die herkömmliche Art, Blöcke / Lambdas zu deklarieren, unterstützte auch keine optionalen Argumente:

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

Ruby 1.9 unterstützt jedoch optionale Argumente auch mit der alten Syntax:

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

Wenn Sie Ruby1.9 für Leopard oder Linux erstellen möchten, lesen Sie diesen Artikel (schamlose Eigenwerbung).

Webmat
quelle
Optionale Parameter innerhalb von Lambda wurden dringend benötigt. Ich bin froh, dass sie in 1.9 hinzugefügt wurden. Ich gehe davon aus, dass Blöcke dann auch optionale Parameter haben können (in 1.9)?
mpd
Sie demonstrieren keine Standardparameter in Blöcken, nur Lambdas
Bilderstürmer
11

Kurze Antwort: Was zählt, ist was returntut: Lambda kehrt aus sich selbst zurück und proc kehrt aus sich selbst UND der Funktion zurück, die es aufgerufen hat.

Was weniger klar ist, ist, warum Sie jeden verwenden möchten. Lambda ist das, was wir erwarten, dass die Dinge im Sinne einer funktionalen Programmierung funktionieren sollten. Grundsätzlich handelt es sich um eine anonyme Methode, bei der der aktuelle Bereich automatisch gebunden wird. Von den beiden ist Lambda dasjenige, das Sie wahrscheinlich verwenden sollten.

Proc hingegen ist sehr nützlich, um die Sprache selbst zu implementieren. Beispielsweise können Sie mit ihnen "if" -Anweisungen oder "for" -Schleifen implementieren. Jede im proc gefundene Rückgabe wird aus der Methode zurückgegeben, die sie aufgerufen hat, und nicht nur aus der "if" -Anweisung. So funktionieren Sprachen, so funktionieren "if" -Anweisungen. Ich vermute, Ruby verwendet dies unter der Decke und sie haben es nur enthüllt, weil es mächtig schien.

Sie würden dies nur dann wirklich benötigen, wenn Sie neue Sprachkonstrukte wie Schleifen, if-else-Konstrukte usw. erstellen.

Evan Moran
quelle
1
"Lambda kehrt aus sich selbst zurück und Proc kehrt aus sich selbst heraus UND die Funktion, die es aufgerufen hat" ist einfach falsch und ein sehr häufiges Missverständnis. Ein Proc ist ein Abschluss und kehrt von der Methode zurück, mit der er erstellt wurde. Siehe meine vollständige Antwort an anderer Stelle auf der Seite.
ComDubh
10

Eine gute Möglichkeit, dies zu erkennen, besteht darin, dass Lambdas in ihrem eigenen Bereich ausgeführt werden (als wäre es ein Methodenaufruf), während Procs als im Einklang mit der aufrufenden Methode ausgeführt angesehen werden kann. Zumindest ist dies eine gute Methode, um zu entscheiden, welche Methode verwendet werden soll in jedem Fall.

krusty.ar
quelle
8

Ich habe keine Kommentare zur dritten Methode in der Quest "proc" bemerkt, die veraltet ist, aber in 1.8 und 1.9 anders gehandhabt wird.

Hier ist ein ziemlich ausführliches Beispiel, das es einfach macht, die Unterschiede zwischen den drei ähnlichen Aufrufen zu erkennen:

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
Dave Rapin
quelle
1
Matz hatte erklärt, dass er vorhatte, es abzulehnen, weil es verwirrend war, dass proc und Proc.new unterschiedliche Ergebnisse zurückgaben. In 1.9 verhalten sie sich jedoch gleich (proc ist ein Alias ​​für Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin
@banister: procgab ein Lambda in 1.8 zurück; Es wurde nun behoben, dass ein Proc in 1.9 zurückgegeben wird - dies ist jedoch eine bahnbrechende Änderung. daher nicht mehr zu empfehlen
Gishu
Ich denke, die Spitzhacke sagt in einer Fußnote irgendwo, dass Proc effektiv beraubt ist oder so. Ich habe nicht die genaue Seitenzahl.
Dertoni
7

Closures in Ruby bietet einen guten Überblick darüber, wie Blöcke, Lambda und Proc in Ruby mit Ruby funktionieren.

swrobel
quelle
Ich hörte auf, dies zu lesen, nachdem ich gelesen hatte: "Eine Funktion kann nicht mehrere Blöcke akzeptieren - was gegen das Prinzip verstößt, dass Verschlüsse frei als Werte weitergegeben werden können." Blöcke sind keine Verschlüsse. Procs sind und eine Funktion kann mehrere Procs akzeptieren.
ComDubh
5

Lambda funktioniert wie erwartet, wie in anderen Sprachen.

Das Kabel Proc.newist überraschend und verwirrend.

Die von returnin proc erstellte Anweisung Proc.newgibt nicht nur die Kontrolle nur von sich selbst zurück, sondern auch von der Methode, die sie einschließt .

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

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

Sie können argumentieren, dass Proc.newCode genau wie Block in die einschließende Methode einfügt. Aber Proc.newein Objekt erstellt, während der Block sind Teil eines Objekts.

Und es gibt einen weiteren Unterschied zwischen Lambda und Proc.new, nämlich den Umgang mit (falschen) Argumenten. Lambda beschwert sich darüber, Proc.newignoriert zusätzliche Argumente oder betrachtet das Fehlen von Argumenten als Null.

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"

Übrigens, procin Ruby 1.8 wird ein Lambda erstellt, während sich in Ruby 1.9+ so verhält Proc.new, was wirklich verwirrend ist.

schwach
quelle
3

Um die Antwort von Accordion Guy näher zu erläutern:

Beachten Sie, dass Proc.newein Proc erstellt wird, indem ein Block übergeben wird. Ich glaube, das lambda {...}wird eher als eine Art Literal analysiert als als ein Methodenaufruf, der einen Block übergibt. returnWenn Sie aus einem Block heraus arbeiten, der an einen Methodenaufruf angehängt ist, wird von der Methode und nicht vom Block zurückgekehrt, und der Proc.newFall ist ein Beispiel dafür.

(Dies ist 1.8. Ich weiß nicht, wie dies zu 1.9 übersetzt wird.)

Peeja
quelle
3

Ich bin etwas spät dran, aber es gibt eine großartige, aber wenig bekannte Sache Proc.new, die in Kommentaren überhaupt nicht erwähnt wird. Wie durch Dokumentation :

Proc::newkann ohne Block nur innerhalb einer Methode mit einem angehängten Block aufgerufen werden. In diesem Fall wird dieser Block in dasProc Objekt konvertiert .

Das heißt, lassen Sie Proc.newuns Ertragsmethoden verketten:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!
Aleksei Matiushkin
quelle
Interessanterweise macht es dasselbe wie das Deklarieren eines &blockArguments in der def, aber ohne dies in der def arg-Liste tun zu müssen.
Jrochkind
2

Es ist hervorzuheben, dass returnin einem Proc die lexikalisch einschließende Methode zurückgegeben wird, dh die Methode, in der der Proc erstellt wurde , nicht die Methode, die den Proc aufgerufen hat. Dies ist eine Folge der Schließungseigenschaft von procs. Der folgende Code gibt also nichts aus:

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

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

foo

Obwohl der Proc ausgeführt wird foobar, wurde er erstellt foound damit die returnExits foo, nicht nur foobar. Wie Charles Caldwell oben schrieb, hat es ein GOTO-Gefühl. Meiner Meinung nach returnist es in einem Block in Ordnung, der in seinem lexikalischen Kontext ausgeführt wird, aber viel weniger intuitiv, wenn es in einem Prozess verwendet wird, der in einem anderen Kontext ausgeführt wird.

ComDubh
quelle
1

Der Unterschied im Verhalten mit returnist meiner Meinung nach der wichtigste Unterschied zwischen den 2. Ich bevorzuge auch Lambda, weil es weniger tippt als Proc.new :-)

Orion Edwards
quelle
2
So aktualisieren Sie: procs können jetzt mit erstellt werden proc {}. Ich bin mir nicht sicher, wann dies in Kraft getreten ist, aber es ist (etwas) einfacher, als Proc.new eingeben zu müssen.
Aceofbassgreg