62 votes

Expliquer une bizarrerie d'évaluation paresseuse

Je suis en train de lire le livre de Hadley Wickhams sur Github, en particulier cette partie sur l'évaluation paresseuse . Il y donne un exemple des conséquences d'une évaluation paresseuse, dans la partie avec add/adders fonctions. Permettez-moi de citer ce passage :

Cette [évaluation paresseuse] est importante lors de la création de fermetures avec lapply ou une boucle :

add <- function(x) {
  function(y) x + y
}
adders <- lapply(1:10, add)
adders[[1]](10)
adders[[10]](10)

x est évaluée paresseusement la première fois que vous appelez l'un des adder de l'additionneur. A ce stade, la boucle est terminée et la valeur finale de x est 10. Par conséquent, toutes les fonctions d'addition ajouteront 10 à leur valeur finale. entrée, ce qui n'est probablement pas ce que vous souhaitiez ! Le fait de forcer manuellement l'évaluation résout le problème :

add <- function(x) {
  force(x)
  function(y) x + y
}
adders2 <- lapply(1:10, add)
adders2[[1]](10)
adders2[[10]](10)

Je n'ai pas l'impression de comprendre cette partie, et l'explication est minimale. Quelqu'un pourrait-il développer cet exemple particulier et expliquer ce qui se passe ? La phrase "à ce stade, la boucle est terminée et la valeur finale de x est 10" me laisse particulièrement perplexe. Quelle boucle ? Quelle valeur finale, où ? Il doit y avoir quelque chose de simple qui m'échappe, mais je ne le vois pas. Merci beaucoup d'avance.

3 votes

Notez que la réponse à cette question a changé à partir de R 3.2.0, voir ma réponse ci-dessous.

0 votes

Complément au commentaire de @jhin : Bien que lapply() a changé au cours des dernières années, la fonction purrr::map() qui est destiné à être utilisé partout où lapply() se comporte toujours comme l'ancien lapply() vis-à-vis des environnements partagés de fermetures. Toutefois, je ne compterais pas sur cet "anachronisme" de la part de la Commission européenne. purrr::map() de rester dans le coin, car il sera probablement corrigé dans les prochaines versions.

0 votes

@jhin En fait, je suppose que le tutoriel de hadley est construit directement à partir de github, donc le lire après la version 3.2.0 de R est maintenant assez bizarre car cette version a rendu inutile toute la section sur l'évaluation paresseuse dans ce tutoriel : il n'y a plus de différence avec adders y adders2 Les sorties de l'entreprise !

57voto

jhin Points 2166

Ce n'est plus vrai depuis R 3.2.0 !

La ligne correspondante dans le journal des modifications lit :

Les fonctions d'ordre supérieur, telles que les fonctions apply et Reduce(), imposent désormais des arguments à l'utilisateur. forcent les arguments aux fonctions qu'elles appliquent afin d'éliminer les interactions indésirables entre l'évaluation paresseuse et la capture de variables dans les fermetures.

Et en effet :

add <- function(x) {
  function(y) x + y
}
adders <- lapply(1:10, add)
adders[[1]](10)
# [1] 11
adders[[10]](10)
# [1] 20

36voto

Paul Hiemstra Points 28390

Le but de :

adders <- lapply(1:10, function(x)  add(x) )

consiste à créer une liste de add la première ajoute 1 à son entrée, la deuxième ajoute 2, etc. L'évaluation paresseuse fait que R attend de créer réellement les fonctions d'additionneurs jusqu'à ce que vous commenciez réellement à appeler les fonctions. Le problème est qu'après avoir créé la première fonction additionneuse, x est augmenté par le lapply boucle, se terminant à une valeur de 10. Lorsque vous appelez la première fonction d'addition, l'évaluation paresseuse construit maintenant la fonction, obtenant la valeur de x . Le problème est que l'original x n'est plus égal à un, mais à la valeur à la fin de l'élément lapply boucle, c'est-à-dire 10.

Par conséquent, l'évaluation paresseuse fait en sorte que toutes les fonctions de l'additionneur attendent après la fin de l'étape d'évaluation. lapply La boucle a terminé de construire réellement la fonction. Puis ils construisent leur fonction avec la même valeur, c'est-à-dire 10. La solution proposée par Hadley est de forcer x à évaluer directement, ce qui permet d'éviter l'évaluation paresseuse et d'obtenir les bonnes fonctions avec les bonnes x valeurs.

4 votes

Ok, laissez-moi reformuler pour voir si j'ai bien compris. Lorsque nous appelons lapply R se souvient en quelque sorte de la structure des 10 fonctions additionnelles, mais n'évalue pas encore x. Lorsque nous appelons la première fonction additionnelle, R se dit : "Ah, voyons ce que c'est", prend x, qui vaut déjà 10 à ce moment-là depuis l'appel à lapply, et évalue la première fonction additionnelle appelée comme 10 + y. Il en va de même pour les autres fonctions additionnelles, ce qui les rend toutes identiques. C'est probablement grossier, mais est-ce bien la logique de la chose ?

0 votes

Je crois que c'est le cas.

1 votes

@hadley Quand j'appelle la première fonction adder, la boucle lapply est déjà terminée. Où exactement la fonction additionneuse cherche-t-elle à trouver x ? Pourquoi la valeur de x = 10 persiste-t-elle ?

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