Mise à jour 2 @G. Grothendieck a signalé deux approches. La seconde modifie l'environnement de fonction à l'intérieur d'une fonction. Cela résout mon problème de trop de répétitions de codage. Je ne sais pas si c'est une bonne méthode pour passer la vérification CRAN lors de la création de mes scripts dans un package. Je mettrai à jour à nouveau quand j'aurai quelques conclusions.
Mise à jour
J'essaie de passer beaucoup de variables d'argument d'entrée à f2
Et je ne veux pas indexer chaque variable à l'intérieur de la fonction comme env$c, env$d, env$calls
, C'est pourquoi j'ai essayé d'utiliser with
dans f5
et f6
(un f2
modifié). Cependant, assign
ne fonctionne pas avec with
à l'intérieur du {}
, Déplacer assign
à l'extérieur de with
fera l'affaire mais dans mon cas réel J'ai quelques assign
dans les expressions with
que je ne sais pas comment les retirer facilement de la fonction with
.
Voici un exemple:
## In the <environment: R_GlobalEnv>
a <- 1
b <- 2
f1 <- function(){
c <- 3
d <- 4
f2 <- function(P){
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1()
La fonction f2
Est à l'intérieur de f1
, Lorsque f2
Est appelé, il recherche les variables calls,c,d
Dans l'environnement environment(f1)
. Voilà ce que je voulais.
Cependant, lorsque je veux utiliser f2
Également dans les autres fonctions, je définirai plutôt cette fonction dans l'environnement global, appelez-la f4
.
f4 <- function(P){
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
Cela ne fonctionnera pas, car il recherchera calls,c,d
Dans l'environnement global au lieu de l'intérieur d'une fonction où la fonction est appelée. Par exemple:
f3 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f4(P=0) ## or replace here with f5(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f3()
Le moyen sûr doit être de définir calls,c,d
Dans les arguments d'entrée de f4
, Puis de passer ces paramètres dans f4
. Cependant, dans mon cas, il y a trop de variables à passer dans cette fonction f4
Et il vaudrait mieux que je puisse le passer comme un environnement et dire à f4
De ne pas regarder dans le Global (environment(f4)
), ne regardez à l'intérieur de environment
que lorsque f3
est appelé.
La façon dont je le résous maintenant est d'utiliser l'environnement comme une liste et d'utiliser la fonction with
.
f5 <- function(P,liste){
with(liste,{
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
)
}
f3 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f5(P=0,as.list(environment())) ## or replace here with f5(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f3()
Cependant, maintenant assign("calls", calls+1, inherits=TRUE)
ne fonctionne pas comme il se doit puisque assign
ne modifie pas l'objet d'origine. La variable calls
est connectée à une fonction d'optimisation où la fonction objectif est f5
. C'est la raison pour laquelle j'utilise assign
au lieu de passer calls
comme arguments d'entrée. L'utilisation de attach
n'est pas non plus claire pour moi. Voici ma façon de corriger le problème assign
:
f7 <- function(P,calls,liste){
##calls <<- calls+1
##browser()
assign("calls", calls+1, inherits=TRUE,envir = sys.frame(-1))
print(calls)
with(liste,{
print(paste('with the listed envrionment, calls=',calls))
return(P+c+d)
}
)
}
########
##################
f8 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
##browser()
##v[i] <- f4(P=0) ## or replace here with f5(P=0)
v[i] <- f7(P=0,calls,liste=as.list(environment()))
c <- c+1
d <- d+1
}
f7(P=0,calls,liste=as.list(environment()))
print(paste('final call number',calls))
return(v)
}
f8()
Je ne sais pas comment cela doit être fait dans R. Suis-je dans la bonne direction, surtout lorsque je passe le contrôle CRAN? Quelqu'un a des indices à ce sujet?
(1) Passer l'environnement de l'appelant. Vous pouvez explicitement passer l'environnement parent et y indexer. Essaye ça:
f2a <- function(P, env = parent.frame()) {
env$calls <- env$calls + 1
print(env$calls)
return(P + env$c + env$d)
}
a <- 1
b <- 2
# same as f1 except f2 removed and call to f2 replaced with call to f2a
f1a <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2a(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1a()
(2) Réinitialiser l'environnement de la fonction appelée consiste à réinitialiser l'environnement de f2b
Dans f1b
Comme indiqué ici:
f2b <- function(P) {
calls <<- calls + 1
print(calls)
return(P + c + d)
}
a <- 1
b <- 2
# same as f1 except f2 removed, call to f2 replaced with call to f2b
# and line marked ## at the beginning is new
f1b <- function(){
environment(f2b) <- environment() ##
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2b(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1b()
(3) Macro utilisant eval.parent (substitute (...)) Encore une autre approche consiste à définir une construction de type macro qui injecte efficacement le corps de f2c
En ligne dans f1c1
. Ici, f2c
Est identique à f2b
À l'exception de la ligne calls <- calls + 1
(Pas de <<-
Nécessaire) et de l'habillage du corps entier dans eval.parent(substitute({...}))
. f1c
Est identique à f1a
Sauf que l'appel à f2a
Est remplacé par un appel à f2c
.
f2c <- function(P) eval.parent(substitute({
calls <- calls + 1
print(calls)
return(P + c + d)
}))
a <- 1
b <- 2
f1c <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2c(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1c()
(4) defmacro C'est presque la même que la dernière solution sauf qu'elle utilise defmacro
dans le paquet gtools pour définir la macro plutôt que de le faire nous-mêmes. (Voir également le package Rcmdr pour une autre version de defmacro.) En raison du fonctionnement de defmacro
, nous devons également passer calls
mais comme c'est une macro et non une fonction, cela lui dit simplement de remplacer calls
in et n'est pas la même chose que de passer calls
à une fonction.
library(gtools)
f2d <- defmacro(P, calls, expr = {
calls <- calls + 1
print(calls)
return(P + c + d)
})
a <- 1
b <- 2
f1d <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2d(P=0, calls)
c <- c+1
d <- d+1
}
return(v)
}
f1d()
On pourrait également utiliser une fonction qui redéfinit d'autres fonctions dans l'environnement spécifié.
test_var <- "global"
get_test_var <- function(){
return(test_var)
}
some_function <- function(){
test_var <- "local"
return(get_test_var())
}
some_function() # Returns "global". Not what we want here...
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
some_function2 <- function(){
test_var <- "local"
# define function locally
get_test_var2 <- function(){
return(test_var)
}
return(get_test_var2())
}
some_function2() # Returns "local", but 'get_test_var2' can't be used in other places.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
add_function_to_envir <- function(my_function_name, to_envir) {
script_text <- capture.output(eval(parse(text = my_function_name)))
script_text[1] <- paste0(my_function_name, " <- ", script_text[1])
eval(parse(text = script_text), envir = to_envir)
}
some_function3 <- function(){
test_var <- "local"
add_function_to_envir("get_test_var", environment())
return(get_test_var())
}
some_function3() # Returns "local" and we can use 'get_test_var' from anywhere.
Ici, add_function_to_envir(my_function_name, to_envir)
capture le script de la fonction, l'analyse et le réévalue dans le nouvel environnement.
Remarque: le nom de la fonction pour my_function_name
Doit être entre guillemets.
En général, je dirais que toute variable nécessaire à l'intérieur d'une fonction doit être transmise via ses arguments. De plus, si sa valeur est nécessaire plus tard, vous la renvoyez de la fonction. Ne pas le faire peut conduire assez rapidement à des résultats étranges, par exemple que faire si plusieurs fonctions définissent une variable x
, laquelle doit être utilisée. Si le nombre de variables est plus important, vous créez une structure de données personnalisée pour celui-ci, par exemple les mettre dans une liste nommée.
Chaque fois que j'utilise des fonctions imbriquées et ne transmets pas les variables en tant qu'arguments, mais que je les transmets à la place avec ...
, J'utilise la fonction suivante dans toutes les fonctions imbriquées pour obtenir des variables de l'environnement parent.
LoadVars <- function(variables, ...){
for (var in 1:length(variables)) {
v <- get(variables[var], envir = parent.frame(n=2))
assign(variables[var], v, envir = parent.frame(n=1))
}
}
A l'intérieur d'une fonction imbriquée, j'ai ensuite LoadVars(c("foo", "bar"))
.
Cette approche est utile dans le sens où vous ne transmettez que les variables dont vous avez besoin, comme lorsque vous transmettez les variables via des arguments.
approche 2
Cependant, il est simple de réécrire cette fonction pour charger toutes les variables de la fonction parent - ou plus haut si nécessaire, augmentez simplement le n
valeur dans parent.frame
à partir de sa valeur d'origine de 2
.
LoadVars <- function(){
variables <- ls(envir = parent.frame(n=2))
for (var in 1:length(variables)) {
v <- get(variables[var], envir = parent.frame(n=2))
assign(variables[var], v, envir = parent.frame(n=1))
}
}
Exemple
a <- 1
A <- function(...){
b <- 2
printf("A, a = %s", a)
printf("A, b = %s", b)
B()
}
B <- function(...){
LoadVars()
printf("B, a = %s", a)
printf("B, b = %s", b)
}
A()
Si vous ne chargez pas de variables dans B
, alors B
est capable de charger a
car il s'agit d'une variable d'environnement global, mais pas b
qui est situé dans A()
.
Production:
[1] "A, a = 1"
[1] "A, b = 2"
[1] "B, a = 1"
[1] "B, b = 2"