247 votes

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
  • par 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;

Appelez

f (1,2)
f (23+55,5)
f (12+3, 44*11)

Quelles sont les règles, s'il vous plaît ?

560voto

dhg Points 26700

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 avons une fonction avec un effet secondaire. Cette fonction imprime quelque chose et renvoie un Int .

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

Maintenant, nous allons définir deux fonctions qui acceptent Int qui sont exactement les mêmes, sauf que l'un d'entre eux 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 à 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 "call-by-value", l'effet secondaire de l'appel de fonction transmis ( something() ) ne s'est produit qu'une seule fois. Cependant, dans la version call-by-name, l'effet secondaire se produit deux fois.

Cela est dû au fait que les fonctions call-by-value calculent la valeur de l'expression passée avant d'appeler la fonction, ce qui fait que la fonction même est accessible à chaque fois. Au lieu de cela, les fonctions d'appel par nom recalculer la valeur de l'expression transmise à chaque fois qu'on y accède.

0 votes

Serait-il correct de dire que la déclaration de fonction définit comment une fonction sera appelée ?

0 votes

@Jam : Oui, je pense que c'est une déclaration raisonnable. La déclaration de la fonction détermine le comportement by-value ou by-name.

307 votes

J'ai toujours pensé que cette terminologie est inutilement confuse. Une fonction peut avoir de multiples paramètres qui varient dans leur statut "appel par nom" ou "appel par valeur". Ce n'est donc pas qu'une fonction est un appel par nom ou un appel par valeur, c'est que chacune de ses paramètres peut être passer -par-nom ou pass-by-value. En outre, "call-by-name" n'a rien à voir avec noms . => Int est différent type de Int ; c'est une "fonction sans argument qui génère un message de type Int " vs juste Int . Une fois que vous avez des fonctions de première classe, vous ne besoin de d'inventer une terminologie par nom pour décrire cela.

52voto

Behrooz Tabesh Points 311

Voici un exemple donné par 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
appeler par son 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 le 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 nom : 7 * 7 -> 49
Ici, l'appel par le nom est plus rapide

test (3+4, 2*4) 

appel par valeur : test(7,2*4) -> test(7, 8) -> 7*7 -> 49
appel par le nom : (3+4) (3+4) -> 7 (3+4) -> 7*7 -> 49
Le résultat est atteint en suivant les mêmes étapes.

1 votes

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

1 votes

L'exemple est tiré de Coursera, principe de programmation en scala. Coursera 1.2. L'appel par le nom devrait se lire def test (x:Int, y: => Int) = x * x Notez que le paramètre y n'est jamais utilisé.

1 votes

Un bon exemple ! Tiré du MOOC de Coursera :)

16voto

kaos12 Points 298

Dans le cas de votre exemple, tous les paramètres seront évalués. avant c'est appelé dans la fonction, puisque vous ne faites que les définir. par 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 seront pas évalués jusqu'à il est appelé dans la fonction.

Ce site petit billet ici l'explique aussi très bien.

10voto

user1496984 Points 139

Pour répéter le point de @Ben dans les commentaires ci-dessus, je pense qu'il est préférable de considérer le "call-by-name" comme un simple sucre syntaxique. L'analyseur syntaxique enveloppe 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 de courir :

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

Vous pourriez aussi écrire :

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

Et exécutez-le comme suit pour obtenir le même effet :

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

0 votes

Je pense que vous vouliez dire : <!-- langage : lang-scala --> def callAlsoByName(x : () => Int) = { println("x1=" + x()) println("x2=" + x()) } et ensuite : <!-- language : lang-js --> callAlsoByName(() => something()) Je ne pense pas que vous ayez besoin des accolades autour de something() dans ce dernier appel. Note : J'ai essayé d'éditer simplement votre réponse mais mon édition a été rejetée par les réviseurs disant que cela devrait être un commentaire ou une réponse séparée à la place.

0 votes

Apparemment, vous ne pouvez pas utiliser coloration syntaxique dans les commentaires donc ignorez juste la partie "<!-- language : lang-scala -->" ! J'aurais bien édité mon propre commentaire mais vous n'avez le droit de le faire que dans les 5 minutes :)

1 votes

J'ai aussi rencontré ce problème récemment. C'est bien de le penser conceptuellement mais Scala fait la différence entre => T y () => T . Une fonction qui prend le premier type comme paramètre, n'acceptera pas le second, scala stocke suffisamment d'informations dans le fichier @ScalaSignature pour générer une erreur à la compilation. Le bytecode pour les deux => T y () => T est le même cependant et est un Function0 . Voir cette question pour plus de détails.

6voto

guykaplan Points 9

Je vais essayer d'expliquer par un cas d'utilisation simple plutôt que par un simple exemple.

Imaginez que vous voulez construire un "application nagger" qui t'embêtera à chaque fois depuis la dernière fois que tu t'es fait embêter.

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, la nagger ne fonctionnera que si elle passe par le nom. la raison est que, lors du passage par valeur, il sera réutilisé et donc la valeur ne sera pas réévaluée alors que lors du passage par nom, la valeur sera réévaluée à chaque fois que les variables sont accédées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X