Wann sollte Lambda und wann Proc.new verwendet werden?
Frage
In Ruby 1.8 gibt es subtile Unterschiede zwischen proc/lambda einerseits und Proc.new
auf dem anderen.
- Was sind diese Unterschiede?
- Können Sie Richtlinien für die Auswahl geben?
- In Ruby 1.9 sind Proc und Lambda unterschiedlich.Was ist das Problem?
Lösung
Ein weiterer wichtiger, aber subtiler Unterschied zwischen Prozessen, die mit erstellt wurden lambda
und Prozesse, die mit erstellt wurden Proc.new
ist, wie sie damit umgehen return
Stellungnahme:
- In einem
lambda
-erstellt proc, derreturn
Die Anweisung kehrt nur vom Prozess selbst zurück - In einem
Proc.new
-erstellt proc, derreturn
Die Aussage ist etwas überraschender:es gibt die Kontrolle nicht nur vom Proc zurück, sondern auch von der Methode, die den Prozess umschließt!
Hier ist lambda
-erstellte Prozesse return
in 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 Proc.new
-erstellte Prozesse return
das Gleiche tun.Sie werden gleich einen dieser Fälle sehen, in denen Ruby das vielgepriesene 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"
Aufgrund dieses überraschenden Verhaltens (und des geringeren Tippaufwands) tendiere ich dazu, die Verwendung zu bevorzugen lambda
über Proc.new
bei der Erstellung von Prozessen.
Andere Tipps
Zur weiteren Erläuterung:
Joey sagt, dass das Rückkehrverhalten von Proc.new
ist überraschend.Wenn man jedoch bedenkt, 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 in Bezug auf die Arität (Anzahl der Argumente) flexibel sind, Lambdas hingegen nicht.Für Blöcke müssen nicht alle Argumente angegeben werden, für Methoden jedoch (es sei denn, es wird ein Standardwert angegeben).Während die Bereitstellung des Standard-Lambda-Arguments in Ruby 1.8 keine Option ist, wird es jetzt in Ruby 1.9 mit der alternativen Lambda-Syntax unterstützt (wie von Webmat angegeben):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Und Michiel de Mare (der OP) hat Unrecht, wenn er sagt, dass sich Procs und Lambda in Ruby 1.9 mit Arity gleich verhalten.Ich habe überprüft, dass sie immer noch das oben angegebene Verhalten von 1.8 beibehalten.
break
Anweisungen machen weder in Procs noch in Lambdas wirklich viel Sinn.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 sich im Wesentlichen um eine Methode handelt und man niemals von der obersten Ebene einer Methode abbrechen würde.
next
, redo
, Und raise
verhalten sich in Procs und Lambdas gleich.Wohingegen retry
ist in beiden Fällen nicht zulässig und löst eine Ausnahme aus.
Und schließlich die proc
Die Methode sollte 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 dabei Proc.new
.
Für weitere Informationen kann ich O'Reilly's wärmstens empfehlen Die Ruby-Programmiersprache Das ist meine Quelle für die meisten dieser Informationen.
ich fand diese Seite Das zeigt, was der Unterschied zwischen Proc.new
Und lambda
Sind.Der Seite zufolge besteht der einzige Unterschied darin, dass ein Lambda streng hinsichtlich der Anzahl der Argumente ist, die es akzeptiert Proc.new
wandelt fehlende Argumente in um nil
.Hier ist eine Beispiel-IRB-Sitzung, 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 "hello", "world" => "helloworld" irb(main):004:0> p.call "hello", "world" => "helloworld" irb(main):005:0> l.call "hello" ArgumentError: wrong number of arguments (1 for 2) from (irb):1 from (irb):5:in `call' from (irb):5 from :0 irb(main):006:0> p.call "hello" TypeError: can't convert nil into String from (irb):2:in `+' from (irb):2 from (irb):6:in `call' from (irb):6 from :0
Die Seite empfiehlt außerdem die Verwendung von Lambda, 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 angesichts eines so unbedeutenden Unterschieds scheint es in der Durchschnittssituation 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 allzu viel ändern würden (verlassen Sie sich aber nicht auf mein Wort, es scheint, als hätten Sie von einigen Änderungen gehört, also Da liege ich wahrscheinlich falsch.
Proc ist älter, aber die Semantik von return ist für mich höchst kontraintuitiv (zumindest als ich die Sprache lernte), weil:
- Wenn Sie proc verwenden, verwenden Sie höchstwahrscheinlich eine Art Funktionsparadigma.
- Proc kann aus dem umschließenden Bereich zurückkehren (siehe vorherige Antworten), was im Grunde genommen ein Goto ist und höchst funktionsunfähig ist.
Lambda ist funktionell sicherer und einfacher zu verstehen – ich verwende es immer anstelle von proc.
Zu den feinen Unterschieden kann ich nicht viel sagen.Allerdings kann ich darauf hinweisen, dass Ruby 1.9 nun 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.Auch die herkömmliche Art der Block-/Lambdas-Deklaration unterstützte optionale Argumente nicht:
# 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 auch mit der alten Syntax optionale Argumente:
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, schauen Sie sich das an Dieser Artikel (schamlose Eigenwerbung).
Kurze Antwort:Was zählt, ist was return
tut:Lambda kehrt aus sich selbst zurück, und Proc kehrt aus sich selbst UND der Funktion zurück, die es aufgerufen hat.
Weniger klar ist, warum Sie jedes verwenden möchten.Lambda ist das, was wir von den Dingen im Sinne der funktionalen Programmierung erwarten.Es handelt sich im Grunde um eine anonyme Methode, bei der der aktuelle Bereich automatisch gebunden wird.Von den beiden sollten Sie wahrscheinlich Lambda verwenden.
Proc hingegen ist wirklich nützlich für die Implementierung der Sprache selbst.Beispielsweise können Sie damit „if“-Anweisungen oder „for“-Schleifen implementieren.Jede im Prozess gefundene Rückgabe wird von der Methode zurückgegeben, die sie aufgerufen hat, nicht nur von der „if“-Anweisung.So funktionieren Sprachen, so funktionieren „Wenn“-Anweisungen. Ich vermute also, dass Ruby dies im Verborgenen verwendet und es nur offengelegt hat, weil es mächtig schien.
Sie benötigen dies nur dann wirklich, wenn Sie neue Sprachkonstrukte wie Schleifen, if-else-Konstrukte usw. erstellen.
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 inline mit der aufrufenden Methode ausgeführt angesehen werden können. Dies ist zumindest eine gute Möglichkeit, zu entscheiden, welche Methode verwendet werden soll in jedem Fall.
Ich habe keine Kommentare zur dritten Methode in der Frage, „proc“, bemerkt, die veraltet ist, aber in 1.8 und 1.9 anders gehandhabt wird.
Hier ist ein ziemlich ausführliches Beispiel, das die Unterschiede zwischen den drei ähnlichen Aufrufen leicht erkennen lässt:
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
Verschlüsse in Ruby ist ein guter Überblick darüber, wie Blöcke, Lambda und Proc in Ruby mit Ruby funktionieren.
Lambda funktioniert wie erwartet, wie in anderen Sprachen.
Die verkabelte Proc.new
ist überraschend und verwirrend.
Der return
Anweisung in proc erstellt von Proc.new
wird die Kontrolle nicht nur von sich selbst zurückgeben, sondern auch aus der es umschließenden Methode.
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Das kann man argumentieren Proc.new
Fügt Code in die umschließende Methode ein, genau wie Block.Aber Proc.new
Erstellt ein Objekt, während Blöcke vorhanden sind Teil von ein Objekt.
Und es gibt noch einen weiteren Unterschied zwischen Lambda und Proc.new
, das ist ihr Umgang mit (falschen) Argumenten.Lambda beschwert sich darüber, während Proc.new
ignoriert 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, proc
in Ruby 1.8 wird ein Lambda erstellt, während es sich in Ruby 1.9+ so verhält Proc.new
, was wirklich verwirrend ist.
Um die Antwort von Accordion Guy näher zu erläutern:
Beachte das Proc.new
Erstellt einen Proc-Ausgang, indem ihm ein Block übergeben wird.Ich glaube das lambda {...}
wird als eine Art Literal geparst und nicht als Methodenaufruf, der einen Block übergibt. return
Ein Aufruf aus dem Inneren eines Blocks, der an einen Methodenaufruf angehängt ist, wird von der Methode zurückgegeben, nicht vom Block Proc.new
Der Fall ist ein Beispiel dafür.
(Das ist 1,8.Ich weiß nicht, wie sich das auf 1.9 übertragen lässt.)
Ich bin etwas spät dran, aber es gibt eine großartige, aber wenig bekannte Sache Proc.new
wird in den Kommentaren überhaupt nicht erwähnt.Wie bei Dokumentation:
Proc::new
kann ohne Block nur innerhalb einer Methode mit angehängtem Block aufgerufen werden Block wird in den umgewandeltProc
Objekt.
Das gesagt, Proc.new
Verketten wir ertragsvermittelnde Methoden:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
Der Unterschied im Verhalten mit return
ist meiner Meinung nach der wichtigste Unterschied zwischen den beiden.Ich bevorzuge auch Lambda, weil es weniger tippt als Proc.new :-)
Es lohnt sich, das zu betonen return
in a proc kehrt von der lexikalisch einschließenden Methode zurück, d. h. die Methode, in der der Prozess erstellt wurde, nicht die Methode, die den Prozess aufgerufen hat.Dies ist eine Folge der Schließungseigenschaft von Prozessen.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 Prozess in ausgeführt wird foobar
, es wurde in erstellt foo
und so return
Ausgänge foo
, nicht nur foobar
.Wie Charles Caldwell oben schrieb, hat es ein GOTO-Gefühl.Meiner Meinung nach, return
ist in einem Block, der in seinem lexikalischen Kontext ausgeführt wird, in Ordnung, aber viel weniger intuitiv, wenn er in einem Prozess verwendet wird, der in einem anderen Kontext ausgeführt wird.