In dem Beispiel, das Sie angegeben haben, wird nur Call-by-Value verwendet. Daher werde ich ein neues, einfacheres Beispiel geben, das den Unterschied zeigt.
Nehmen wir zunächst an, wir haben eine Funktion mit Nebeneffekt. Diese Funktion druckt etwas aus und gibt dann ein zurück Int
.
def something() = {
println("calling something")
1 // return value
}
Jetzt definieren wir zwei Funktionen, die Int
Argumente akzeptieren , die genau gleich sind, außer dass eine das Argument in einem Call-by-Value-Stil ( x: Int
) und die andere in einem Call-by-Name-Stil ( x: => Int
) verwendet.
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Was passiert nun, wenn wir sie mit unserer Nebenwirkungsfunktion aufrufen?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Sie sehen also, dass in der Call-by-Value-Version der Nebeneffekt des übergebenen Funktionsaufrufs ( something()
) nur einmal aufgetreten ist. In der Call-by-Name-Version trat der Nebeneffekt jedoch zweimal auf.
Dies liegt daran, dass Call-by-Value-Funktionen den Wert des übergebenen Ausdrucks vor dem Aufrufen der Funktion berechnen und daher jedes Mal auf denselben Wert zugegriffen wird. Stattdessen berechnen Call-by-Name-Funktionen den Wert des übergebenen Ausdrucks bei jedem Zugriff neu.
=> Int
ist ein anderer Typ alsInt
; Es ist "Funktion ohne Argumente, die einInt
" gegen nur erzeugenInt
. Sobald Sie über erstklassige Funktionen verfügen, müssen Sie keine Call-by-Name-Terminologie erfinden, um dies zu beschreiben.f(2)
als Ausdruck vom Typ kompiliert wirdInt
, wird der generierte Codef
mit Argument aufgerufen2
und das Ergebnis ist der Wert des Ausdrucks. Wenn derselbe Text als Ausdruck vom Typ kompiliert=> Int
wird, verwendet der generierte Code einen Verweis auf eine Art "Codeblock" als Wert des Ausdrucks. In beiden Fällen kann ein Wert dieses Typs an eine Funktion übergeben werden, die einen Parameter dieses Typs erwartet. Ich bin mir ziemlich sicher, dass Sie dies mit variabler Zuweisung tun können, ohne dass ein Parameter in Sicht ist. Was haben Namen oder Anrufe damit zu tun?=> Int
"Funktion ohne Argumente, die ein Int erzeugt" ist, wie unterscheidet es sich von() => Int
? Scala scheint diese unterschiedlich zu behandeln, zum Beispiel=> Int
funktioniert es anscheinend nicht als Typ einesval
, sondern nur als Typ eines Parameters.=> Int
ist eine Annehmlichkeit und wird nicht genau so implementiert, wie es ein Funktionsobjekt ist (vermutlich, warum Sie keine Variablen vom Typ haben können=> Int
, obwohl es keinen fundamentalen Grund gibt, warum dies nicht funktionieren könnte).() => Int
ist explizit eine Funktion ohne Argumente, die ein zurückgebenInt
, das explizit aufgerufen werden muss und als Funktion übergeben werden kann.=> Int
ist eine Art "ProxyInt
", und das einzige, was Sie damit tun können, ist es (implizit) aufzurufen, um das zu erhaltenInt
.Hier ein Beispiel von Martin Odersky:
Wir wollen die Bewertungsstrategie untersuchen und feststellen, welche unter diesen Bedingungen schneller ist (weniger Schritte):
Aufruf nach Wert: Test (2,3) -> 2 * 2 -> 4
Aufruf nach Name: Test (2,3) -> 2 * 2 -> 4
Hier wird das Ergebnis mit der gleichen Anzahl von Schritten erreicht.
Anruf nach Wert: Test (7,8) -> 7 * 7 -> 49
Anruf nach Name: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Hier anrufen nach Wert ist schneller.
call by value: test (7,8) -> 7 * 7 -> 49
call by name: 7 * 7 -> 49
Hier ist call by name schneller
Anruf nach Wert: Test (7,2 * 4) -> Test (7, 8) -> 7 * 7 -> 49
Anruf nach Name: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Das Ergebnis wird in den gleichen Schritten erreicht.
quelle
def test (x:Int, y: => Int) = x * x
, dass der Parameter y niemals verwendet wird.In Ihrem Beispiel werden alle Parameter ausgewertet, bevor sie in der Funktion aufgerufen werden, da Sie sie nur nach Wert definieren . Wenn Sie Ihre Parameter nach Namen definieren möchten, sollten Sie einen Codeblock übergeben:
Auf diese Weise wird der Parameter
x
erst ausgewertet, wenn er in der Funktion aufgerufen wird.Dieser kleine Beitrag hier erklärt dies auch sehr gut.
quelle
Um @ Bens Punkt in den obigen Kommentaren zu wiederholen, denke ich, dass es am besten ist, sich "Call-by-Name" nur als syntaktischen Zucker vorzustellen. Der Parser verpackt die Ausdrücke nur in anonyme Funktionen, damit sie zu einem späteren Zeitpunkt aufgerufen werden können, wenn sie verwendet werden.
In der Tat, anstatt zu definieren
und läuft:
Sie könnten auch schreiben:
Führen Sie es für den gleichen Effekt wie folgt aus:
quelle
=> T
und() => T
. Eine Funktion, die den ersten Typ als Parameter verwendet, akzeptiert den zweiten nicht. Scala speichert genügend Informationen in@ScalaSignature
Anmerkungen, um einen Kompilierungszeitfehler dafür auszulösen. Der Bytecode für beide=> T
und() => T
ist jedoch gleich und ist aFunction0
. Weitere Informationen finden Sie in dieser Frage .Ich werde versuchen, dies anhand eines einfachen Anwendungsfalls zu erklären, anstatt nur ein Beispiel zu liefern
Stellen Sie sich vor, Sie möchten eine "Nagger-App" erstellen , die Sie jedes Mal nervt , seit Sie das letzte Mal genagt wurden.
Untersuchen Sie die folgenden Implementierungen:
In der obigen Implementierung funktioniert der Nagger nur bei der Übergabe des Namens. Der Grund dafür ist, dass er beim Übergeben des Werts erneut verwendet wird und daher der Wert nicht neu bewertet wird, während bei der Übergabe des Namens der Wert jedes Mal neu bewertet wird Zeit, auf die auf die Variablen zugegriffen wird
quelle
In der Regel sind Parameter für Funktionen By-Value-Parameter. Das heißt, der Wert des Parameters wird bestimmt, bevor er an die Funktion übergeben wird. Was aber, wenn wir eine Funktion schreiben müssen, die einen Ausdruck als Parameter akzeptiert, den wir erst auswerten möchten, wenn er in unserer Funktion aufgerufen wird? Unter diesen Umständen bietet Scala Call-by-Name-Parameter an.
Ein Call-by-Name-Mechanismus übergibt einen Codeblock an den Angerufenen. Jedes Mal, wenn der Angerufene auf den Parameter zugreift, wird der Codeblock ausgeführt und der Wert berechnet.
quelle
Wie ich annehme,
call-by-value
übergibt die oben diskutierte Funktion nur die Werte an die Funktion. LautMartin Odersky
It handelt es sich um eine Evaluierungsstrategie, gefolgt von einer Scala, die die wichtige Rolle bei der Funktionsbewertung spielt. Aber machen Sie es einfachcall-by-name
. Es ist wie eine Übergabe der Funktion als Argument an die Methode auch bekannt alsHigher-Order-Functions
. Wenn die Methode auf den Wert des übergebenen Parameters zugreift, ruft sie die Implementierung übergebener Funktionen auf. wie nachstehend:Erstellen Sie die Methode gemäß dem @ dhg-Beispiel zuerst wie folgt:
Diese Funktion enthält eine
println
Anweisung und gibt einen ganzzahligen Wert zurück. Erstellen Sie die Funktion, die Argumente alscall-by-name
:Dieser Funktionsparameter definiert eine anonyme Funktion, die einen ganzzahligen Wert zurückgegeben hat. Darin
x
enthalten ist eine Definition der Funktion, die0
Argumente übergeben hat, aber einen Rückgabewert enthältint
, und unseresomething
Funktion enthält dieselbe Signatur. Wenn wir die Funktion aufrufen, übergeben wir die Funktion als Argument ancallByName
. Aber im Fallecall-by-value
seiner nur übergeben Sie den ganzzahligen Wert an die Funktion. Wir nennen die Funktion wie folgt:In dieser wird unsere
something
Methode zweimal aufgerufen, denn wenn wir auf den Wert vonx
incallByName
method zugreifen , ruft sie die Definition vonsomething
method auf.quelle
Call by Value ist ein allgemeiner Anwendungsfall, wie in vielen Antworten hier erläutert.
Ich werde versuchen, den Anruf mit Namen anhand der folgenden Anwendungsfälle einfacher zu demonstrieren
Beispiel 1:
Ein einfaches Beispiel / ein Anwendungsfall für einen Aufruf nach Namen befindet sich unter der Funktion, die die Funktion als Parameter verwendet und die verstrichene Zeit angibt.
Beispiel 2:
Apache Spark (mit Scala) verwendet die Protokollierung mithilfe des Namensaufrufs (siehe
Logging
Merkmal), bei dem träge bewertet wird, oblog.isInfoEnabled
die unten beschriebene Methode verwendet wird oder nicht.quelle
Bei einem Call by Value wird der Wert des Ausdrucks zum Zeitpunkt des Funktionsaufrufs vorberechnet und dieser bestimmte Wert als Parameter an die entsprechende Funktion übergeben. Der gleiche Wert wird während der gesamten Funktion verwendet.
Während bei einem Call by Name der Ausdruck selbst als Parameter an die Funktion übergeben wird und nur innerhalb der Funktion berechnet wird, wenn dieser bestimmte Parameter aufgerufen wird.
Der Unterschied zwischen Call by Name und Call by Value in Scala lässt sich anhand des folgenden Beispiels besser verstehen:
Code-Auszug
Ausgabe
Im obigen Codeausschnitt wird für den Funktionsaufruf CallbyValue (System.nanoTime ()) die System- Nanozeit vorberechnet und diesem vorberechneten Wert wurde ein Parameter an den Funktionsaufruf übergeben.
Im Funktionsaufruf CallbyName (System.nanoTime ()) wird jedoch der Ausdruck "System.nanoTime ())" selbst als Parameter an den Funktionsaufruf übergeben, und der Wert dieses Ausdrucks wird berechnet, wenn dieser Parameter innerhalb der Funktion verwendet wird .
Beachten Sie die Funktionsdefinition der CallbyName-Funktion, bei der ein => Symbol zwischen dem Parameter x und seinem Datentyp steht. Das dortige Symbol zeigt an, dass die Funktion nach Namenstyp aufgerufen wird.
Mit anderen Worten, die Funktionsargumente für den Aufruf nach Wert werden vor der Eingabe der Funktion einmal ausgewertet, die Funktionsargumente für den Aufruf nach Namen werden jedoch nur dann innerhalb der Funktion ausgewertet, wenn sie benötigt werden.
Hoffe das hilft!
quelle
Hier ist ein kurzes Beispiel, das ich codiert habe, um einem Kollegen von mir zu helfen, der gerade am Scala-Kurs teilnimmt. Was ich interessant fand, ist, dass Martin die zuvor in der Vorlesung vorgestellte && Frage nicht als Beispiel verwendet hat. Auf jeden Fall hoffe ich, dass dies hilft.
Die Ausgabe des Codes lautet wie folgt:
quelle
Parameter werden normalerweise als Wert übergeben, was bedeutet, dass sie ausgewertet werden, bevor sie im Funktionskörper eingesetzt werden.
Sie können den Aufruf eines Parameters nach Namen erzwingen, indem Sie beim Definieren der Funktion den Doppelpfeil verwenden.
quelle
Es gibt bereits viele fantastische Antworten auf diese Frage im Internet. Ich werde eine Zusammenstellung mehrerer Erklärungen und Beispiele schreiben, die ich zu diesem Thema gesammelt habe, für den Fall, dass jemand es hilfreich findet
EINFÜHRUNG
Call-by-Value (CBV)
In der Regel sind Parameter für Funktionen Call-by-Value-Parameter. Das heißt, die Parameter werden von links nach rechts ausgewertet, um ihren Wert zu bestimmen, bevor die Funktion selbst ausgewertet wird
Call-by-Name (CBN)
Aber was ist, wenn wir eine Funktion schreiben müssen, die einen Ausdruck als Parameter akzeptiert, den wir erst auswerten, wenn er in unserer Funktion aufgerufen wird? Unter diesen Umständen bietet Scala Call-by-Name-Parameter an. Dies bedeutet, dass der Parameter unverändert an die Funktion übergeben wird und seine Bewertung nach der Substitution erfolgt
Ein Call-by-Name-Mechanismus übergibt einen Codeblock an den Aufruf. Jedes Mal, wenn der Aufruf auf den Parameter zugreift, wird der Codeblock ausgeführt und der Wert berechnet. Im folgenden Beispiel wird verzögert eine Nachricht gedruckt, die zeigt, dass die Methode eingegeben wurde. Als nächstes druckt verzögert eine Nachricht mit ihrem Wert. Schließlich gibt verzögert 't' zurück:
Vor- und Nachteile für jeden Fall
CBN: + Wird häufiger beendet * siehe oben stehende Terminierung * + Hat den Vorteil, dass ein Funktionsargument nicht ausgewertet wird, wenn der entsprechende Parameter bei der Auswertung des Funktionskörpers nicht verwendet wird. - Es ist langsamer, es werden mehr Klassen erstellt (dh das Programm nimmt länger zu laden) und es verbraucht mehr Speicher.
CBV: + Es ist oft exponentiell effizienter als CBN, da es diese wiederholte Neuberechnung von Argumentausdrücken vermeidet, die mit Namen aufgerufen werden. Es wertet jedes Funktionsargument nur einmal aus. + Es spielt viel besser mit imperativen Effekten und Nebenwirkungen, da Sie in der Regel viel besser wissen, wann Ausdrücke ausgewertet werden. -Es kann zu einer Schleife während der Parameterauswertung führen * siehe oben stehende Terminierung *
Was ist, wenn die Kündigung nicht garantiert ist?
- Wenn die CBV-Bewertung eines Ausdrucks e endet, endet auch die CBN-Bewertung von e. - Die andere Richtung ist nicht wahr
Beispiel für die Nichtbeendigung
Betrachten Sie zuerst den Ausdruck (1, Schleife)
CBN: first (1, loop) → 1 CBV: first (1, loop) → Argumente dieses Ausdrucks reduzieren. Da es sich um eine Schleife handelt, werden die Argumente unendlich reduziert. Es endet nicht
UNTERSCHIEDE IN JEDEM FALLVERHALTEN
Definieren wir einen Methodentest, der sein wird
Fall1 Test (2,3)
Da wir mit bereits ausgewerteten Argumenten beginnen, werden die gleichen Schritte für Call-by-Value und Call-by-Name ausgeführt
Fall2 Test (3 + 4,8)
In diesem Fall führt Call-by-Value weniger Schritte aus
Fall3-Test (7, 2 * 4)
Wir vermeiden die unnötige Berechnung des zweiten Arguments
Fall4-Test (3 + 4, 2 * 4)
Anderer Ansatz
Nehmen wir zunächst an, wir haben eine Funktion mit Nebeneffekt. Diese Funktion druckt etwas aus und gibt dann ein Int zurück.
Jetzt definieren wir zwei Funktionen, die genau dieselben Int-Argumente akzeptieren, mit der Ausnahme, dass eines das Argument in einem Call-by-Value-Stil (x: Int) und das andere in einem Call-by-Name-Stil (x: => Int).
Was passiert nun, wenn wir sie mit unserer Nebenwirkungsfunktion aufrufen?
Sie sehen also, dass in der Call-by-Value-Version der Nebeneffekt des übergebenen Funktionsaufrufs (etwas ()) nur einmal aufgetreten ist. In der Call-by-Name-Version trat der Nebeneffekt jedoch zweimal auf.
Dies liegt daran, dass Call-by-Value-Funktionen den Wert des übergebenen Ausdrucks vor dem Aufrufen der Funktion berechnen und daher jedes Mal auf denselben Wert zugegriffen wird. Call-by-Name-Funktionen berechnen jedoch bei jedem Zugriff den Wert des übergebenen Ausdrucks neu.
BEISPIELE, WO ES BESSER IST, CALL-BY-NAME ZU VERWENDEN
Von: https://stackoverflow.com/a/19036068/1773841
Einfaches Leistungsbeispiel: Protokollierung.
Stellen wir uns eine Schnittstelle wie diese vor:
Und dann so verwendet:
Wenn die info-Methode nichts unternimmt (weil beispielsweise die Protokollierungsstufe höher konfiguriert wurde), wird computeTimeSpent nie aufgerufen, was Zeit spart. Dies passiert häufig bei Protokollierern, bei denen häufig Manipulationen an Zeichenfolgen auftreten, die im Vergleich zu den zu protokollierenden Aufgaben teuer sein können.
Beispiel für die Richtigkeit: Logikoperatoren.
Sie haben wahrscheinlich Code wie diesen gesehen:
Stellen Sie sich vor, Sie würden die && Methode wie folgt deklarieren:
Wenn ref dann null ist, wird eine Fehlermeldung angezeigt, da isSomething bei einer Nullreferenz aufgerufen wird, bevor es an && übergeben wird. Aus diesem Grund lautet die tatsächliche Erklärung:
quelle
Wenn Sie ein Beispiel durchgehen, können Sie den Unterschied besser verstehen.
Definieren wir eine einfache Funktion, die die aktuelle Zeit zurückgibt:
Jetzt definieren wir eine Funktion mit Namen , die zweimal verzögert um eine Sekunde gedruckt wird:
Und eine Eins nach Wert :
Nennen wir jetzt jeden:
Das Ergebnis sollte den Unterschied erklären. Das Snippet finden Sie hier .
quelle
CallByName
wird bei Verwendung aufgerufen undcallByValue
bei jeder Begegnung mit der Anweisung aufgerufen.Beispielsweise:-
Ich habe eine Endlosschleife, dh wenn Sie diese Funktion ausführen, werden wir nie
scala
aufgefordert.Eine
callByName
Funktion verwendet die obigeloop
Methode als Argument und wird niemals in ihrem Körper verwendet.Bei der Ausführung der
callByName
Methode finden wir kein Problem (wir erhalten einescala
Rückmeldung), da wir die Schleifenfunktion innerhalb dercallByName
Funktion nicht verwenden können.Eine
callByValue
Funktion nimmt die obigeloop
Methode als Parameter, da das Ergebnis innerhalb der Funktion oder des Ausdrucks ausgewertet wird, bevor die äußere Funktion dort durch eineloop
rekursiv ausgeführte Funktion ausgeführt wird, und wir werden nie wiederscala
aufgefordert.quelle
Sieh dir das an:
y: => Int ist ein Aufruf mit Namen. Was als Namensaufruf übergeben wird, ist add (2, 1). Dies wird träge ausgewertet. Die Ausgabe auf der Konsole lautet also "mul", gefolgt von "add", obwohl add anscheinend zuerst aufgerufen wird. Call by Name fungiert als Übergabe eines Funktionszeigers.
Wechseln Sie nun von y: => Int zu y: Int. Die Konsole zeigt "add" gefolgt von "mul"! Übliche Art der Bewertung.
quelle
Ich denke nicht, dass alle Antworten hier die richtige Rechtfertigung haben:
Beim Aufruf nach Wert werden die Argumente nur einmal berechnet:
Sie können oben sehen, dass alle Argumente ausgewertet werden, ob sie nicht benötigt werden. Normalerweise
call-by-value
können sie schnell sein, aber nicht immer wie in diesem Fall.Wenn die Bewertungsstrategie
call-by-name
wäre, wäre die Zerlegung gewesen:Wie Sie oben sehen können, mussten wir nie bewerten
4 * 11
und haben daher ein wenig Berechnung gespeichert, was manchmal von Vorteil sein kann.quelle