Il existe déjà de nombreuses réponses fantastiques à cette question sur Internet. J'écrirai une compilation de plusieurs explications et exemples que j'ai rassemblés sur le sujet, au cas où quelqu'un pourrait le trouver utile
INTRODUCTION
appel par valeur (CBV)
En règle générale, les paramètres des fonctions sont des paramètres d'appel par valeur; c'est-à-dire que les paramètres sont évalués de gauche à droite pour déterminer leur valeur avant d'évaluer la fonction elle-même
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
appel par nom (CBN)
Mais que se passe-t-il si nous devons écrire une fonction qui accepte comme paramètre une expression que nous ne devons pas évaluer jusqu'à ce qu'elle soit appelée dans notre fonction? Dans ce cas, Scala propose des paramètres d'appel par nom. Cela signifie que le paramètre est passé tel quel dans la fonction et que sa valorisation a lieu après substitution
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Un mécanisme d'appel par nom transmet un bloc de code à l'appel et chaque fois que l'appel accède au paramètre, le bloc de code est exécuté et la valeur est calculée. Dans l'exemple suivant, le délai imprime un message démontrant que la méthode a été entrée. Ensuite, le délai imprime un message avec sa valeur. Enfin, les retours retardés «t»:
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
En méthode différée
Obtention du temps en nano secondes
Param: 2027245119786400
POUR ET CONTRE CHAQUE CAS
CBN:
+ se termine plus souvent * vérifiez ci-dessous au-dessus de la terminaison * + a l'avantage qu'un argument de fonction n'est pas évalué si le paramètre correspondant n'est pas utilisé dans l'évaluation du corps de la fonction -il est plus lent, il crée plus de classes (ce qui signifie que le programme prend plus long à charger) et il consomme plus de mémoire.
CBV:
+ Il est souvent exponentiellement plus efficace que CBN, car il évite ce recalcul répété d'arguments qu'implique l'appel par nom. Il n'évalue chaque argument de fonction qu'une seule fois + Il joue beaucoup mieux avec les effets impératifs et les effets secondaires, car vous avez tendance à savoir beaucoup mieux quand les expressions seront évaluées. -Il peut conduire à une boucle lors de l'évaluation de ses paramètres * vérifier ci-dessus la terminaison *
Et si la résiliation n'est pas garantie?
-Si l'évaluation CBV d'une expression e se termine, alors l'évaluation CBN de e se termine aussi -L'autre sens n'est pas vrai
Exemple de non-résiliation
def first(x:Int, y:Int)=x
Considérez d'abord l'expression (1, boucle)
CBN: premier (1, boucle) → 1 CBV: premier (1, boucle) → réduire les arguments de cette expression. Puisque l'un est une boucle, il réduit les arguments à l'infini. Il ne se termine pas
DIFFÉRENCES DANS CHAQUE COMPORTEMENT
Définissons un test de méthode qui sera
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Test du cas 1 (2,3)
test(2,3) → 2*2 → 4
Puisque nous commençons avec des arguments déjà évalués, ce sera la même quantité d'étapes pour l'appel par valeur et l'appel par nom
Test du cas 2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
Dans ce cas, l'appel par valeur effectue moins d'étapes
Test du cas 3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
On évite le calcul inutile du deuxième argument
Test Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Une approche différente
Tout d'abord, supposons que nous ayons une fonction avec un effet secondaire. Cette fonction imprime quelque chose puis retourne un Int.
def something() = {
println("calling something")
1 // return value
}
Maintenant, nous allons définir deux fonctions qui acceptent des arguments Int qui sont exactement les mêmes, sauf que l'un prend l'argument dans un style appel par valeur (x: Int) et l'autre dans un style appel par nom (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Maintenant, que se passe-t-il lorsque nous les appelons avec notre fonction d'effet secondaire?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Vous pouvez donc voir que dans la version appel par valeur, l'effet secondaire de l'appel de fonction transmis (quelque chose ()) ne s'est produit qu'une seule fois. Cependant, dans la version appel par nom, l'effet secondaire s'est produit deux fois.
En effet, les fonctions appel par valeur calculent la valeur de l'expression transmise avant d'appeler la fonction, donc la même valeur est accédée à chaque fois. Cependant, les fonctions d'appel par nom recalculent la valeur de l'expression transmise à chaque fois qu'elle est accédée.
EXEMPLES OERE IL EST MIEUX D'UTILISER L'APPEL PAR NOM
De: https://stackoverflow.com/a/19036068/1773841
Exemple de performance simple: journalisation.
Imaginons une interface comme celle-ci:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
Et puis utilisé comme ceci:
logger.info("Time spent on X: " + computeTimeSpent)
Si la méthode info ne fait rien (parce que, disons, le niveau de journalisation a été configuré pour une valeur supérieure à celle-là), computeTimeSpent n'est jamais appelé, ce qui permet de gagner du temps. Cela se produit souvent avec les enregistreurs, où l'on voit souvent une manipulation de chaîne qui peut être coûteuse par rapport aux tâches enregistrées.
Exemple de correction: opérateurs logiques.
Vous avez probablement vu du code comme celui-ci:
if (ref != null && ref.isSomething)
Imaginez que vous déclariez la méthode && comme ceci:
trait Boolean {
def &&(other: Boolean): Boolean
}
puis, chaque fois que ref est nul, vous obtiendrez une erreur car isSomething sera appelé sur une référence nulle avant d'être passé à &&. Pour cette raison, la déclaration réelle est:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
est un type différent deInt
; c'est "fonction d'aucun argument qui va générer unInt
" vs justeInt
. Une fois que vous avez des fonctions de première classe, vous n'avez pas besoin d'inventer la terminologie de l'appel par nom pour décrire cela.