Si je comprends bien, en Scala, une fonction peut être appelée soit
Par exemple, étant donné les déclarations suivantes, savons-nous comment la fonction sera appelée?
Déclaration:
def f (x:Int, y:Int) = x;
Appel
f (1,2)
f (23+55,5)
f (12+3, 44*11)
Quelles sont les règles s'il vous plaît?
L'exemple que vous avez donné utilise uniquement appel par valeur, je vais donc vous donner un nouvel exemple, plus simple, qui montre la différence.
Premièrement, supposons que nous ayons une fonction avec un effet secondaire. Cette fonction imprime quelque chose puis renvoie un Int
.
def something() = {
println("calling something")
1 // return value
}
Nous allons maintenant définir deux fonctions qui acceptent Int
arguments exactement identiques, sauf que l’un prend l’argument dans un style d’appel par valeur (x: Int
) et l’autre dans un appel d’appel. style -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 (something()
) ne s'est produit qu'une seule fois. Cependant, dans la version 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. Ainsi, la valeur identique est utilisée à chaque fois. Cependant, les fonctions appel par nom recalculer la valeur de l'expression transmise à chaque accès.
Voici un exemple de Martin Odersky:
def test (x:Int, y: Int)= x*x
Nous voulons examiner la stratégie d'évaluation et déterminer laquelle est la plus rapide (moins d'étapes) dans ces conditions:
test (2,3)
appel par valeur: test (2,3) -> 2 * 2 -> 4
appel par nom: test (2,3) -> 2 * 2 -> 4
Ici, le résultat est atteint avec le même nombre de pas.
test (3+4,8)
appel par valeur: test (7,8) -> 7 * 7 -> 49
appel par nom: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Ici, l'appel par valeur est plus rapide.
test (7,2*4)
appel par valeur: test (7,8) -> 7 * 7 -> 49
appel par son nom: 7 * 7 -> 49
Ici, appeler par son nom est plus rapide
test (3+4, 2*4)
appel par valeur: test (7,2 * 4) -> test (7, 8) -> 7 * 7 -> 49
appel par nom: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Le résultat est atteint dans les mêmes étapes.
Dans le cas de votre exemple, tous les paramètres seront évalués avant que soit appelé dans la fonction, car vous ne les définissez que en valeur . Si vous voulez définir vos paramètres par nom , vous devez passer un bloc de code:
def f(x: => Int, y:Int) = x
De cette façon, le paramètre x
ne sera pas évalué tant que il ne sera pas appelé dans la fonction.
Ce petit post ici explique bien cela aussi.
Pour répéter le point @ Ben dans les commentaires ci-dessus, je pense qu'il est préférable de penser à "appeler par son nom" comme un simple sucre syntaxique. L'analyseur encapsule simplement les expressions dans des fonctions anonymes, de sorte qu'elles puissent être appelées ultérieurement, lorsqu'elles sont utilisées.
En effet, au lieu de définir
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
et en cours d'exécution:
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Vous pouvez aussi écrire:
def callAlsoByName(x: () => Int) = {
println("x1=" + x())
println("x2=" + x())
}
Et exécutez-le comme suit pour le même effet:
callAlsoByName(() => {something()})
calling something
x1=1
calling something
x2=1
J'essaierai d'expliquer par un simple cas d'utilisation plutôt que par un simple exemple
Imaginez que vous vouliez construire une "application nagger" qui vous tracera à chaque fois depuis le dernier moment où vous vous êtes fait avoir.
Examinez les implémentations suivantes:
object main {
def main(args: Array[String]) {
def onTime(time: Long) {
while(time != time) println("Time to Nag!")
println("no nags for you!")
}
def onRealtime(time: => Long) {
while(time != time) println("Realtime Nagging executed!")
}
onTime(System.nanoTime())
onRealtime(System.nanoTime())
}
}
Dans l'implémentation ci-dessus, le nagger ne fonctionnera qu'en passant par nom, la raison en est que, en passant par valeur, il sera réutilisé et par conséquent, la valeur ne sera pas réévaluée. en passant par nom, la valeur sera réévaluée à chaque accès aux variables
En règle générale, les paramètres des fonctions sont des paramètres par valeur; c'est-à-dire que la valeur du paramètre est déterminée avant qu'il ne soit passé à la fonction. Mais que se passe-t-il si nous devons écrire une fonction qui accepte en tant que paramètre une expression que nous ne voulons pas évaluer tant qu'elle n'est pas appelée dans notre fonction? Dans ce cas, Scala propose des paramètres d'appel par nom.
Un mécanisme d'appel par nom transmet un bloc de code à l'appelé et chaque fois que l'appelant accède au paramètre, le bloc de code est exécuté et la valeur est calculée.
object Test {
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)
t
}
}
1. C: /> scalac Test.scala 2. scala Test 3. Méthode différée 4. Obtenir du temps en nanosecondes 5. Param: 81303808765843 6. Obtenir le temps en nanosecondes
Voici un exemple rapide que j'ai codé pour aider un de mes collègues qui suit actuellement le cours Scala. Ce que j’ai trouvé intéressant, c’est que Martin n’a pas pris pour exemple la réponse à la question && présentée précédemment dans la conférence. En tout cas j'espère que cela aide.
val start = Instant.now().toEpochMilli
val calc = (x: Boolean) => {
Thread.sleep(3000)
x
}
def callByValue(x: Boolean, y: Boolean): Boolean = {
if (!x) x else y
}
def callByName(x: Boolean, y: => Boolean): Boolean = {
if (!x) x else y
}
new Thread(() => {
println("========================")
println("Call by Value " + callByValue(false, calc(true)))
println("Time " + (Instant.now().toEpochMilli - start) + "ms")
println("========================")
}).start()
new Thread(() => {
println("========================")
println("Call by Name " + callByName(false, calc(true)))
println("Time " + (Instant.now().toEpochMilli - start) + "ms")
println("========================")
}).start()
Thread.sleep(5000)
La sortie du code sera la suivante:
========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================
L'appel par valeur est un cas d'utilisation général, comme l'expliquent de nombreuses réponses ici.
Appel par nom transmet un bloc de code à l'appelant. Chaque fois que l'appelant accède au paramètre, le bloc de code est exécuté et la valeur est calculée.
Je vais essayer de démontrer appel par nom de manière plus simple avec les cas d'utilisation ci-dessous
Exemple 1:
Un exemple/cas d'utilisation simple d'appel par nom est situé sous la fonction, qui prend fonction en tant que paramètre et indique le temps écoulé.
/**
* Executes some code block and prints to stdout the
time taken to execute the block
for interactive testing and debugging.
*/
def time[T](f: => T): T = {
val start = System.nanoTime()
val ret = f
val end = System.nanoTime()
println(s"Time taken: ${(end - start) / 1000 / 1000} ms")
ret
}
Exemple 2:
Apache spark (avec scala) utilise la journalisation en utilisant appel par nom, voir Logging
trait dans lequel son évalue paresseusement si log.isInfoEnabled
ou non à l'aide de la méthode ci-dessous.
protected def logInfo(msg: => String) {
if (log.isInfoEnabled) log.info(msg)
}
Comme je le suppose, la fonction call-by-value
décrite ci-dessus ne transmet que les valeurs à la fonction. Selon Martin Odersky
C'est une stratégie d'évaluation suivie d'un Scala qui joue un rôle important dans l'évaluation des fonctions. Mais, faites simple en call-by-name
. son comme un passe la fonction comme argument de la méthode également connu sous le nom de Higher-Order-Functions
. Lorsque la méthode accède à la valeur du paramètre passé, elle appelle l'implémentation des fonctions passées. comme ci-dessous:
Selon l'exemple @dhg, créez d'abord la méthode en tant que:
def something() = {
println("calling something")
1 // return value
}
Cette fonction contient une instruction println
et renvoie une valeur entière. Créez la fonction, qui a des arguments en tant que call-by-name
:
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Ce paramètre de fonction définit une fonction anonyme renvoyant une valeur entière. Dans cette x
contient une définition de fonction qui a 0
passé les arguments mais renvoie int
value et notre fonction something
contient la même signature. Lorsque nous appelons la fonction, nous passons la fonction en tant qu'argument à callByName
. Mais dans le cas de call-by-value
, il ne fait que transmettre la valeur entière à la fonction. Nous appelons la fonction comme ci-dessous:
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Dans cette notre méthode something
appelée deux fois, parce que lorsque nous accédons à la valeur de la méthode x
dans la méthode callByName
, son appel à la définition de la méthode something
.
Les paramètres sont généralement passés par valeur, ce qui signifie qu'ils seront évalués avant d'être substitués dans le corps de la fonction.
Vous pouvez forcer un paramètre à être appelé par son nom en utilisant la double flèche lors de la définition de la fonction.
// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1
// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)
// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))
// will not terminate, since loop(2) will evaluate.
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ...
Suivre un exemple devrait vous aider à mieux comprendre la différence.
Définissons une fonction simple qui renvoie l'heure actuelle:
def getTime = System.currentTimeMillis
Nous allons maintenant définir une fonction, par nom , qui imprime deux fois en retard d'une seconde:
def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}
Et une valeur un :
def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}
Maintenant appelons chacun:
getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325
getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846
Le résultat devrait expliquer la différence. L'extrait est disponible ici .
Il y a déjà beaucoup de réponses fantastiques à cette question sur Internet. Je rédigerai une compilation de plusieurs explications et exemples rassemblés sur le sujet, au cas où quelqu'un le jugerait utile.
INTRODUCTION
appel par valeur (CBV)
En règle générale, les paramètres des fonctions sont des paramètres appel par valeur; c'est-à-dire que les paramètres sont évalués de gauche à droite pour déterminer leur valeur avant que la fonction elle-même ne soit évaluée
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 en tant que paramètre une expression que nous ne devons pas évaluer tant qu'elle n'est pas appelée dans notre fonction? Dans ce cas, Scala propose des paramètres d'appel par nom. Ce qui signifie que le paramètre est passé tel quel à la fonction et que sa valorisation a lieu après la 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, différé imprime un message indiquant que la méthode a été entrée. Ensuite, différé 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
Obtenir du temps en nanosecondes
Param: 2027245119786400
AVANTAGES ET INCONVÉNIENTS POUR CHAQUE AFFAIRE
CBN: + termine plus souvent * vérifier ci-dessus au-dessus de la terminaison * + présente 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 de temps à charger) et consomme plus de mémoire.
CBV: + Il est souvent plus efficace de manière exponentielle que CBN, car il évite ce recalcul répété des expressions d'arguments entraînant un appel par son nom. Il évalue chaque argument de fonction 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-dessous la terminaison ci-dessus *
Que se passe-t-il 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 direction n'est pas vraie
Exemple de non-résiliation
def first(x:Int, y:Int)=x
Considérons d'abord l'expression (1, boucle)
CBN: premier (1, boucle) → 1 CBV: premier (1, boucle) → réduit les arguments de cette expression. Comme on est une boucle, les arguments sont réduits à l'infini. Il ne se termine pas
DIFFÉRENCES DANS CHAQUE COMPORTEMENT DE CAS
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
Cas1 test (2,3)
test(2,3) → 2*2 → 4
Puisque nous commençons avec des arguments déjà évalués, le nombre d'étapes correspondant à l'appel par valeur et à l'appel par nom sera identique.
Cas2 test (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, appel par valeur effectue moins d'étapes
Cas3 test (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Nous évitons le calcul inutile du deuxième argument
Test de cas4 (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
approche différente
Premièrement, supposons que nous ayons une fonction avec un effet secondaire. Cette fonction imprime quelque chose puis renvoie un Int.
def something() = {
println("calling something")
1 // return value
}
Nous allons maintenant définir deux fonctions qui acceptent les 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
Ainsi, vous pouvez 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 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. La même valeur est donc consultée à chaque fois. Cependant, les fonctions appel par nom recalculent la valeur de l'expression transmise à chaque accès.
EXEMPLES O IL IS MIEUX UTILISER 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 ça:
logger.info("Time spent on X: " + computeTimeSpent)
Si la méthode info ne fait rien (parce que, par exemple, le niveau de journalisation a été configuré pour une valeur supérieure à celle-là), alors computeTimeSpent n'est jamais appelé, ce qui permet de gagner du temps. Cela se produit souvent avec les enregistreurs, où l'on voit souvent des manipulations de chaînes qui peuvent être coûteuses par rapport aux tâches consignées.
Exemple de correction: opérateurs logiques.
Vous avez probablement déjà vu un code comme celui-ci:
if (ref != null && ref.isSomething)
Imaginez que vous déclareriez la méthode && comme ceci:
trait Boolean {
def &&(other: Boolean): Boolean
}
ensuite, chaque fois que ref est null, vous obtiendrez une erreur car isSomething sera appelé avec une référence null avant d'être passé à &&. Pour cette raison, la déclaration proprement dite est:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
Dans un Call by Value, la valeur de l'expression est pré-calculée au moment de l'appel de la fonction et cette valeur particulière est transmise en tant que paramètre à la fonction correspondante. La même valeur sera utilisée tout au long de la fonction.
Tandis que dans un Appel par nom, l'expression elle-même est transmise en tant que paramètre à la fonction et est uniquement calculée à l'intérieur de la fonction, chaque fois que ce paramètre particulier est appelé.
La différence entre Appel par nom et Appel par valeur dans Scala pourrait être mieux comprise à l'aide de l'exemple ci-dessous:
extrait de code
object CallbyExample extends App {
// function definition of call by value
def CallbyValue(x: Long): Unit = {
println("The current system time via CBV: " + x);
println("The current system time via CBV " + x);
}
// function definition of call by name
def CallbyName(x: => Long): Unit = {
println("The current system time via CBN: " + x);
println("The current system time via CBN: " + x);
}
// function call
CallbyValue(System.nanoTime());
println("\n")
CallbyName(System.nanoTime());
}
sortie
The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521
The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589
Dans l'extrait de code ci-dessus, pour l'appel de fonction CallbyValue (System.nanoTime ()), l'heure nano système est précalculée et une valeur précalculée a été transmise à l'appel de fonction.
Mais dans l'appel CallbyName (System.nanoTime ()), l'expression "System.nanoTime ()) elle-même est transmise en tant que paramètre à l'appel de fonction et la valeur de cette expression est calculée lorsque ce paramètre est utilisé dans la fonction.
Notez la définition de la fonction CallbyName, où il existe un symbole => séparant le paramètre x et son type de données. Ce symbole particulier indique que la fonction est de type appel par nom.
En d'autres termes, les arguments de la fonction call by value sont évalués une fois avant d'entrer dans la fonction, mais les arguments de la fonction call by name ne sont évalués à l'intérieur de la fonction que lorsqu'ils sont nécessaires.
J'espère que cela t'aides!
CallByName
est appelé lorsqu'il est utilisé et callByValue
est appelé chaque fois que l'instruction est rencontrée.
Par exemple:-
J'ai une boucle infinie, c'est-à-dire que si vous exécutez cette fonction, nous n'aurons jamais scala
Invite.
scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int
une fonction callByName
prend la méthode ci-dessus loop
comme argument et elle n'est jamais utilisée à l'intérieur de son corps.
scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int
Lors de l'exécution de la méthode callByName
, nous ne trouvons aucun problème (nous obtenons scala
Invite de retour), car nous ne sommes pas en train d'utiliser la fonction de boucle dans la fonction callByName
.
scala> callByName(1,loop(10))
res1: Int = 1
scala>
une fonction callByValue
prend ci-dessus la méthode loop
en tant que paramètre; en conséquence, la fonction ou l'expression est évaluée avant d'être exécutée par la fonction externe _ par la fonction loop
exécutée de manière récursive et nous n'obtenons jamais scala
Retour rapide.
scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int
scala> callByValue(1,loop(1))
Regarde ça:
object NameVsVal extends App {
def mul(x: Int, y: => Int) : Int = {
println("mul")
x * y
}
def add(x: Int, y: Int): Int = {
println("add")
x + y
}
println(mul(3, add(2, 1)))
}
y: => Int est un appel par son nom. Ce qui est passé comme appel par nom est add (2, 1). Cela sera évalué paresseusement. Ainsi, la sortie sur la console sera "mul" suivie de "add", bien que add semble être appelé en premier. Appel par nom agit comme une sorte de passage d'un pointeur de fonction.
Passons maintenant de y: => Int à y: Int. La console affichera "add" suivie de "mul"! Méthode habituelle d'évaluation.