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?
War es hilfreich?

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, der return Die Anweisung kehrt nur vom Prozess selbst zurück
  • In einem Proc.new-erstellt proc, der return 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:

  1. Wenn Sie proc verwenden, verwenden Sie höchstwahrscheinlich eine Art Funktionsparadigma.
  2. 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. returnEin 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 umgewandelt Proc 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top