Appel par nom vs appel par valeur dans Scala, clarification nécessaire


239

Si je comprends bien, dans Scala, une fonction peut être appelée soit

  • par valeur ou
  • de nom

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?

Réponses:


540

L'exemple que vous avez donné utilise uniquement l'appel par valeur, je vais donc donner un nouvel exemple plus simple qui montre la différence.

Tout d'abord, 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 des Intarguments 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 la fonction passée call ( something()) 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. Au lieu de cela, les fonctions d'appel par nom recalculent la valeur de l'expression transmise à chaque fois qu'elle est accédée.


296
J'ai toujours pensé que cette terminologie prête à confusion. Une fonction peut avoir plusieurs paramètres qui varient dans leur état appel par nom vs état appel par valeur. Ce n'est donc pas qu'une fonction est appelée par nom ou par valeur, c'est que chacun de ses paramètres peut être passe -par-nom ou passe-par-valeur. De plus, "l'appel par nom" n'a rien à voir avec les noms . => Intest un type différent de Int; c'est "fonction d'aucun argument qui va générer un Int" vs juste Int. 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.
Ben

2
@Ben, cela aide à répondre à quelques questions, merci. J'aimerais que plus d'écrits expliquent clairement la sémantique du mot de passe.
Christopher Poile

3
@SelimOber Si le texte f(2)est compilé en tant qu'expression de type Int, le code généré appelle favec argument 2et le résultat est la valeur de l'expression. Si ce même texte est compilé en tant qu'expression de type, => Intle code généré utilise une référence à une sorte de "bloc de code" comme valeur de l'expression. Dans les deux cas, une valeur de ce type peut être transmise à une fonction attendant un paramètre de ce type. Je suis sûr que vous pouvez le faire avec une affectation de variable, sans passer de paramètre en vue. Alors, qu'est-ce que les noms ou les appels ont quelque chose à voir avec ça?
Ben

4
@Ben Donc, si => Intest "fonction d'aucun argument qui génère un Int", en quoi est-ce différent de () => Int? Scala semble les traiter différemment, par exemple => Intne fonctionne apparemment pas comme le type d'un val, seulement comme le type d'un paramètre.
Tim Goodman

5
@TimGoodman Vous avez raison, c'est un peu plus compliqué que je ne le pensais. => Intest une commodité, et il n'est pas implémenté exactement comme un objet fonction (probablement pourquoi vous ne pouvez pas avoir de variables de type => Int, bien qu'il n'y ait aucune raison fondamentale pour laquelle cela ne pourrait pas fonctionner). () => Intest explicitement une fonction sans arguments qui renverra un Int, qui doit être appelé explicitement et peut être passé en tant que fonction. => Intest une sorte de "proxy Int", et la seule chose que vous pouvez faire avec lui est de l'appeler (implicitement) pour obtenir le Int.
Ben

51

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 d'étapes.

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
Appel ici par valeur est plus rapide.

test (7,2*4)

appel par valeur: test (7,8) -> 7 * 7 -> 49
appel par nom: 7 * 7 -> 49
Ici, l'appel par 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.


1
Dans le troisième exemple pour CBV, je pense que vous vouliez dire test (7,8) au lieu de test (7,14)
talonx

1
L'exemple est tiré de Coursera, principe de la programmation scala. Conférence 1.2. L'appel par nom doit lire, def test (x:Int, y: => Int) = x * xnotez que le paramètre y n'est jamais utilisé.
dr jerry

1
Bon exemple! Extrait du MOOC Coursera :)
alxsimo

Ceci est une bonne explication de la différence, mais ne répond pas à la question posée, à savoir lequel des deux appelle Scala
db1234

16

Dans le cas de votre exemple, tous les paramètres seront évalués avant d' être appelés dans la fonction, car vous ne les définissez que par valeur . Si vous souhaitez 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 xne sera évalué qu'après son appel dans la fonction.

Ce petit post ici explique cela aussi très bien.


10

Pour réitérer le point de @ Ben dans les commentaires ci-dessus, je pense qu'il est préférable de penser à "l'appel par nom" comme du sucre syntaxique. L'analyseur encapsule simplement les expressions dans des fonctions anonymes, afin 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 également é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

Je pense que vous vouliez dire: <! - language: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} puis: <! - langue: lang-js -> callAlsoByName (() => quelque chose ()) Je ne pense pas que vous ayez besoin des accolades autour de quelque chose () dans ce dernier appel. Remarque: J'ai essayé de simplement modifier votre réponse, mais ma modification a été rejetée par les critiques, disant qu'elle devrait plutôt être un commentaire ou une réponse distincte.
lambdista

Apparemment, vous ne pouvez pas utiliser la coloration syntaxique dans les commentaires, alors ignorez simplement la partie "<! - language: lang-scala ->"! J'aurais édité mon propre commentaire mais vous n'êtes autorisé à le faire que dans les 5 minutes! :)
lambdista

1
J'ai récemment rencontré cela également. Il est correct de penser conceptuellement comme ça, mais scala différencie entre => Tet () => T. Une fonction qui prend le premier type comme paramètre, n'acceptera pas le second, scala stocke suffisamment d'informations dans l' @ScalaSignatureannotation pour générer une erreur de temps de compilation pour cela. Le bytecode pour les deux => Tet () => Test même si et est Function0. Voir cette question pour plus de détails.
vsnyc

6

Je vais essayer d'expliquer par un cas d'utilisation simple plutôt qu'en fournissant simplement un exemple

Imaginez que vous souhaitiez créer une «application de harcèlement» qui vous harcellerait à chaque fois depuis la dernière fois où vous vous êtes fait harceler.

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 que lors du passage par le nom, la raison est que, lors du passage par la valeur, il sera réutilisé et donc la valeur ne sera pas réévaluée tandis qu'en passant par le nom, la valeur sera réévaluée tous les heure d'accès aux variables


4

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 d'être transmise à la fonction. Mais que se passe-t-il si nous devons écrire une fonction qui accepte comme paramètre une expression que nous ne voulons pas évaluer jusqu'à ce qu'elle soit 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'appelé 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. test scala
 3. En méthode différée
 4. Obtenir le temps en nano secondes
 5. Param: 81303808765843
 6. Obtenir le temps en nano secondes

2

Comme je suppose, la call-by-valuefonction décrite ci-dessus ne transmet que les valeurs à la fonction. Selon Martin OderskyC'est une stratégie d'évaluation suivie par une Scala qui joue un rôle important dans l'évaluation des fonctions. Mais, simplifiez call-by-name. c'est comme passer la fonction comme argument à la méthode aussi connue comme 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 comme suit:

def something() = {
 println("calling something")
 1 // return value
}  

Cette fonction contient une printlninstruction et renvoie une valeur entière. Créez la fonction, qui a des arguments comme call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Ce paramètre de fonction, est de définir une fonction anonyme qui a renvoyé une valeur entière. Dans ceci xcontient une définition de la fonction qui a 0passé des arguments mais retourne une intvaleur et notre somethingfonction contient la même signature. Lorsque nous appelons la fonction, nous lui transmettons un argument callByName. Mais dans le cas de call-by-valuesa seule passe la valeur entière à la fonction. Nous appelons la fonction comme ci-dessous:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

En cela, notre somethingméthode a appelé deux fois, car lorsque nous accédons à la valeur de xin callByNamemethod, son appel à la définition de somethingmethod.


2

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 et 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 l'appel par son nom de manière plus simple avec les cas d'utilisation ci-dessous

Exemple 1:

Un exemple / cas d'utilisation simple de l'appel par nom est en dessous de la fonction, qui prend la fonction en paramètre et donne 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 l'appel par nom, voir Loggingtrait dans lequel son évalue paresseusement si oui log.isInfoEnabledou non à partir de la méthode ci-dessous.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

2

Dans un appel par valeur , la valeur de l'expression est précalculée au moment de l'appel de 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.

Alors que dans un appel par nom , l'expression elle-même est passée en tant que paramètre à la fonction et elle n'est calculée qu'à l'intérieur de la fonction, chaque fois que ce paramètre particulier est appelé.

La différence entre l'appel par nom et l'appel par valeur dans Scala pourrait être mieux comprise avec 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());
}

Production

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 du système est précalculée et cette valeur précalculée a été transmise un paramètre à l'appel de fonction.

Mais dans l' appel de fonction CallbyName (System.nanoTime ()) , l'expression "System.nanoTime ())" est elle-même passée en tant que paramètre à l'appel de fonction et la valeur de cette expression est calculée lorsque ce paramètre est utilisé à l'intérieur de la fonction .

Notez la définition de fonction de la fonction CallbyName, où il y a un symbole => séparant le paramètre x et son type de données. Ce symbole particulier indique que la fonction est d'appel par type de nom.

En d'autres termes, les arguments de la fonction appel par valeur sont évalués une fois avant d'entrer dans la fonction, mais les arguments de la fonction appel par nom ne sont évalués à l'intérieur de la fonction que lorsqu'ils sont nécessaires.

J'espère que cela t'aides!


2

Voici un exemple rapide que j'ai codé pour aider un de mes collègues qui suit actuellement le cours Scala. Ce que je pensais intéressant, c'est que Martin n'a pas utilisé la réponse à la question && présentée plus tôt dans la conférence comme exemple. En tout cas, j'espère que cela vous aidera.

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
========================

1

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) -> ... 

1

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*24

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 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

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 * 749
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 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

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
}

1

L'examen d'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 son nom , qui imprime deux fois retardée d'une seconde:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

Et un par valeur :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Appelons maintenant chacun:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

Le résultat devrait expliquer la différence. L'extrait est disponible ici .


0

CallByNameest invoqué lorsqu'il est utilisé et callByValueest invoqué 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 ne recevrons jamais d' scalainvite.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

une callByNamefonction prend la loopméthode ci-dessus 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 callByNameméthode, nous ne trouvons aucun problème (nous recevons une scalainvite) car nous ne savons pas où utiliser la fonction de boucle à l'intérieur de la callByNamefonction.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

une callByValuefonction prend la loopméthode ci-dessus comme paramètre car un résultat à l'intérieur d'une fonction ou d'une expression est évalué avant d'y exécuter une fonction externe par une loopfonction exécutée récursivement et nous ne recevons jamais d' scalainvite.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

0

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 appelé par son nom. Ce qui est passé comme appel par nom est add (2, 1). Cela sera évalué paresseusement. La sortie sur la console sera donc "mul" suivie de "add", bien que add semble être appelé en premier. L'appel par nom agit comme une sorte de passage d'un pointeur de fonction.
Passez maintenant de y: => Int à y: Int. La console affichera "ajouter" suivi de "mul"! Mode d'évaluation habituel.


-2

Je ne pense pas que toutes les réponses ici font la bonne justification:

En appel par valeur, les arguments sont calculés une seule fois:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

vous pouvez voir ci-dessus que tous les arguments sont évalués s'ils ne le sont pas, ils call-by-valuepeuvent normalement être rapides mais pas toujours comme dans ce cas.

Si la stratégie d'évaluation l'était, call-by-namela décomposition aurait été:

f(12 + 3, 4 * 11)
12 + 3
15

comme vous pouvez le voir ci-dessus, nous n'avons jamais eu besoin d'évaluer 4 * 11et donc économisé un peu de calcul qui peut parfois être bénéfique.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.