web-dev-qa-db-fra.com

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

Si je comprends bien, en 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?

219
JAM

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.

499
dhg

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.

47
Behrooz Tabesh

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.

14
resilva87

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
8
user1496984

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

6
guykaplan

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 
4
sofiene zaghdoudi

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

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)
 }
2
Ram Ghadiyaram

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.

2

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
iblamefish

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 .

1
Maroun

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
}
1
Ignacio Alorre

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!

1
Nijanthan Vijayakumar

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))
0
Puneeth Reddy V

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.

0
Apurva Singh