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 Int
arguments 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.
=> 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.f(2)
est compilé en tant qu'expression de typeInt
, le code généré appellef
avec argument2
et le résultat est la valeur de l'expression. Si ce même texte est compilé en tant qu'expression de type,=> Int
le 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?=> Int
est "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=> Int
ne fonctionne apparemment pas comme le type d'unval
, seulement comme le type d'un paramètre.=> Int
est 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).() => Int
est explicitement une fonction sans arguments qui renverra unInt
, qui doit être appelé explicitement et peut être passé en tant que fonction.=> Int
est une sorte de "proxyInt
", et la seule chose que vous pouvez faire avec lui est de l'appeler (implicitement) pour obtenir leInt
.Voici un exemple de Martin Odersky:
Nous voulons examiner la stratégie d'évaluation et déterminer laquelle est la plus rapide (moins d'étapes) dans ces conditions:
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.
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.
appel par valeur: test (7,8) -> 7 * 7 -> 49
appel par nom: 7 * 7 -> 49
Ici, l'appel par nom est plus rapide
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.
la source
def test (x:Int, y: => Int) = x * x
notez que le paramètre y n'est jamais utilisé.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:
De cette façon, le paramètre
x
ne sera évalué qu'après son appel dans la fonction.Ce petit post ici explique cela aussi très bien.
la source
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
et en cours d'exécution:
Vous pouvez également écrire:
Et exécutez-le comme suit pour le même effet:
la source
=> T
et() => T
. Une fonction qui prend le premier type comme paramètre, n'acceptera pas le second, scala stocke suffisamment d'informations dans l'@ScalaSignature
annotation pour générer une erreur de temps de compilation pour cela. Le bytecode pour les deux=> T
et() => T
est même si et estFunction0
. Voir cette question pour plus de détails.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:
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
la source
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.
la source
Comme je suppose, la
call-by-value
fonction décrite ci-dessus ne transmet que les valeurs à la fonction. SelonMartin Odersky
C'est une stratégie d'évaluation suivie par une Scala qui joue un rôle important dans l'évaluation des fonctions. Mais, simplifiezcall-by-name
. c'est comme passer la fonction comme argument à la méthode aussi connue commeHigher-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:
Cette fonction contient une
println
instruction et renvoie une valeur entière. Créez la fonction, qui a des arguments commecall-by-name
:Ce paramètre de fonction, est de définir une fonction anonyme qui a renvoyé une valeur entière. Dans ceci
x
contient une définition de la fonction qui a0
passé des arguments mais retourne uneint
valeur et notresomething
fonction contient la même signature. Lorsque nous appelons la fonction, nous lui transmettons un argumentcallByName
. Mais dans le cas decall-by-value
sa seule passe la valeur entière à la fonction. Nous appelons la fonction comme ci-dessous:En cela, notre
something
méthode a appelé deux fois, car lorsque nous accédons à la valeur dex
incallByName
method, son appel à la définition desomething
method.la source
L'appel par valeur est un cas d'utilisation général, comme l'expliquent de nombreuses réponses ici.
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é.
Exemple 2:
apache spark (avec scala) utilise la journalisation en utilisant l'appel par nom, voir
Logging
trait dans lequel son évalue paresseusement si ouilog.isInfoEnabled
ou non à partir de la méthode ci-dessous.la source
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
Production
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!
la source
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.
La sortie du code sera la suivante:
la source
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.
la source
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
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
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»:
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
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
Test du cas 1 (2,3)
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)
Dans ce cas, l'appel par valeur effectue moins d'étapes
Test du cas 3 (7, 2 * 4)
On évite le calcul inutile du deuxième argument
Test Case4 (3 + 4, 2 * 4)
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.
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).
Maintenant, que se passe-t-il lorsque nous les appelons avec notre fonction d'effet secondaire?
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:
Et puis utilisé comme ceci:
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:
Imaginez que vous déclariez la méthode && comme ceci:
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:
la source
L'examen d'un exemple devrait vous aider à mieux comprendre la différence.
Définissons une fonction simple qui renvoie l'heure actuelle:
Nous allons maintenant définir une fonction, par son nom , qui imprime deux fois retardée d'une seconde:
Et un par valeur :
Appelons maintenant chacun:
Le résultat devrait expliquer la différence. L'extrait est disponible ici .
la source
CallByName
est invoqué lorsqu'il est utilisé etcallByValue
est 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'
scala
invite.une
callByName
fonction prend laloop
méthode ci-dessus comme argument et elle n'est jamais utilisée à l'intérieur de son corps.Lors de l'exécution de la
callByName
méthode, nous ne trouvons aucun problème (nous recevons unescala
invite) car nous ne savons pas où utiliser la fonction de boucle à l'intérieur de lacallByName
fonction.une
callByValue
fonction prend laloop
mé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 uneloop
fonction exécutée récursivement et nous ne recevons jamais d'scala
invite.la source
Regarde ça:
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.
la source
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:
vous pouvez voir ci-dessus que tous les arguments sont évalués s'ils ne le sont pas, ils
call-by-value
peuvent normalement être rapides mais pas toujours comme dans ce cas.Si la stratégie d'évaluation l'était,
call-by-name
la décomposition aurait été:comme vous pouvez le voir ci-dessus, nous n'avons jamais eu besoin d'évaluer
4 * 11
et donc économisé un peu de calcul qui peut parfois être bénéfique.la source