在R中,如何使函数内的变量在这个函数内得到更低的函数?(附上,环境)

时间:2021-08-17 09:05:11

Update 2 @G. Grothendieck posted two approaches. The second one is changing the function environment inside a function. This solves my problem of too many coding replicates. I am not sure if this is a good method to pass through the CRAN check when making my scripts into a package. I will update again when I have some conclusions.

更新2 @G。Grothendieck发布两种方法。第二个是改变函数内的函数环境。这解决了我太多的编码复制问题。我不确定这是不是一个很好的方法,在把我的脚本变成一个包时通过CRAN检查。当我有一些结论时,我会再次更新。

Update

更新

I am trying to pass a lot of input argument variables to f2 and do not want to index every variable inside the function as env$c, env$d, env$calls, that is why I tried to use with in f5 and f6(a modified f2). However, assign does not work with with inside the {}, moving assign outside with will do the job but in my real case I have a few assigns inside the with expressions which I do not know how to move them out of the with function easily.

我试图将很多输入参数变量传递给f2,不想将函数内的每个变量都作为env$c, env$d, env$调用,这就是为什么我尝试在f5和f6中使用的原因(一个修改的f2)。但是,赋值在{}内不工作,移动分配将会完成任务,但在我的实际情况下,我有一些在表达式中赋值,我不知道如何将它们轻易地从函数中移出。

Here is an example:

这是一个例子:

## 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()

Function f2 is inside f1, when f2 is called, it looks for variables calls,c,d in the environment environment(f1). This is what I wanted.

函数f2在f1中,当f2被调用时,它在环境环境(f1)中寻找变量调用,c,d。这就是我想要的。

However, when I want to use f2 also in the other functions, I will define this function in the Global environment instead, call it f4.

但是,当我想在其他函数中使用f2时,我将在全局环境中定义这个函数,称之为f4。

f4 <- function(P){
  assign("calls", calls+1, inherits=TRUE)
  print(calls)
  return(P+c+d)
}

This won't work, because it will look for calls,c,d in the Global environment instead of inside a function where the function is called. For example:

这不会起作用,因为它将在全局环境中查找调用,c,d,而不是在调用函数的函数内。例如:

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()

The safe way should be define calls,c,d in the input arguments of f4 and then pass these parameters into f4. However, in my case, there are too many variables to be passed into this function f4 and it would be better that I can pass it as an environment and tell f4 do not look in the Global environment(environment(f4)), only look inside the environment when f3 is called.

安全的方法应该在f4的输入参数中定义调用,c,d,然后将这些参数传递给f4。但是,在我的例子中,有太多的变量要传递到这个函数f4中,我可以把它作为一个环境传递给f4,告诉f4不要在全局环境中查看(环境(f4)),只在调用f3时查看环境。

The way I solve it now is to use the environment as a list and use the with function.

我现在解决它的方法是使用环境作为列表并使用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()

However, now assign("calls", calls+1, inherits=TRUE) does not work as it should be since assign does not modify the original object. The variable calls is connected to an optimization function where the objective function is f5. That is the reason I use assign instead of passing calls as an input arguments. Using attach is also not clear to me. Here is my way to correct the assign issue:

但是,现在分配(“调用”,调用+1,继承=TRUE)不工作,因为分配不修改原始对象。变量调用连接到一个优化函数,其中目标函数为f5。这就是我使用赋值而不是传递调用作为输入参数的原因。使用attach也不清楚。这是我纠正分配问题的方法:

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()

I am not sure how this should be done in R. Am I on the right direction, especially when passing through the CRAN check? Anyone has some hints on this?

我不知道该如何在R. am我在正确的方向,特别是当通过CRAN检查?有人有什么建议吗?

3 个解决方案

#1


23  

(1) Pass caller's environment. You can explicitly pass the parent environment and index into it. Try this:

(1)通过调用者的环境。您可以显式地将父环境和索引传递给它。试试这个:

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) Reset called function's environment is to reset the environment of f2b in f1b as shown here:

(2)复位调用函数的环境是重置f1b中的f2b环境,如下所示:

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 using eval.parent(substitute(...)) Yet another approach is to define a macro-like construct which effectively injects the body of f2c inline into f1c1. Here f2c is the same as f2b except for the calls <- calls + 1 line (no <<- needed) and the wrapping of the entire body in eval.parent(substitute({...})). f1c is the same as f1a except the call to f2a is replaced with a call to f2c .

另一种方法是定义一个类似于宏的构造,有效地将f2c的主体插入到f1c1中。这里f2c与f2b相同,除了调用<-调用+ 1行(不需要<<-需要)和整个主体在evd .parent(替换({…}))。f1c与f1a相同,除了对f2a的调用被对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 This is almost the same as the the last solution except it uses defmacro in the gtools package to define the macro rather than doing it ourself. (Also see the Rcmdr package for another defmacro version.) Because of the way defmacro works we must also pass calls but since it's a macro and not a function this just tells it to substitute calls in and is not the same as passing calls to a function.

(4)defmacro与最后一个解决方案几乎相同,只是它在gtools包中使用defmacro来定义宏,而不是自己做。(还可以看到Rcmdr包的另一个defmacro版本。)由于defmacro的工作方式,我们也必须通过调用,但由于它是一个宏而不是一个函数,它只告诉它替换调用,而不像传递函数调用一样。

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()

#2


1  

In general, I would say that any variable that is needed inside a function should be passed on through its arguments. In addition, if its value is needed later you pass it back from the function. Not doing this can quite quickly lead to strange results, e.g. what if there are multiple functions defining a variable x, which one should be used. If the amount of variables is larger, you create a custom data structure for it, e.g. putting them into a named list.

通常,我认为函数内需要的任何变量都应该通过其参数传递。此外,如果它的值是需要的,稍后您将它从函数中传递回来。不这样做会很快导致奇怪的结果,例如,如果有多个函数定义变量x,应该使用哪个函数。如果变量的数量更大,您可以为它创建一个自定义的数据结构,例如将它们放入一个指定的列表中。

#3


1  

One could also use a function that redefines other functions in the specified environment.

还可以使用在指定环境中重新定义其他函数的函数。

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.

Here add_function_to_envir(my_function_name, to_envir) captures the script of the function, parses and reevaluates it in the new environment.

这里add_function_to_envir(my_function_name, to_envir)捕获函数的脚本,并在新环境中对其进行解析和重新计算。

Note: the name of the function for my_function_name needs to be in quotes.

注意:my_function_name函数的名称需要引用。

#1


23  

(1) Pass caller's environment. You can explicitly pass the parent environment and index into it. Try this:

(1)通过调用者的环境。您可以显式地将父环境和索引传递给它。试试这个:

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) Reset called function's environment is to reset the environment of f2b in f1b as shown here:

(2)复位调用函数的环境是重置f1b中的f2b环境,如下所示:

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 using eval.parent(substitute(...)) Yet another approach is to define a macro-like construct which effectively injects the body of f2c inline into f1c1. Here f2c is the same as f2b except for the calls <- calls + 1 line (no <<- needed) and the wrapping of the entire body in eval.parent(substitute({...})). f1c is the same as f1a except the call to f2a is replaced with a call to f2c .

另一种方法是定义一个类似于宏的构造,有效地将f2c的主体插入到f1c1中。这里f2c与f2b相同,除了调用<-调用+ 1行(不需要<<-需要)和整个主体在evd .parent(替换({…}))。f1c与f1a相同,除了对f2a的调用被对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 This is almost the same as the the last solution except it uses defmacro in the gtools package to define the macro rather than doing it ourself. (Also see the Rcmdr package for another defmacro version.) Because of the way defmacro works we must also pass calls but since it's a macro and not a function this just tells it to substitute calls in and is not the same as passing calls to a function.

(4)defmacro与最后一个解决方案几乎相同,只是它在gtools包中使用defmacro来定义宏,而不是自己做。(还可以看到Rcmdr包的另一个defmacro版本。)由于defmacro的工作方式,我们也必须通过调用,但由于它是一个宏而不是一个函数,它只告诉它替换调用,而不像传递函数调用一样。

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()

#2


1  

In general, I would say that any variable that is needed inside a function should be passed on through its arguments. In addition, if its value is needed later you pass it back from the function. Not doing this can quite quickly lead to strange results, e.g. what if there are multiple functions defining a variable x, which one should be used. If the amount of variables is larger, you create a custom data structure for it, e.g. putting them into a named list.

通常,我认为函数内需要的任何变量都应该通过其参数传递。此外,如果它的值是需要的,稍后您将它从函数中传递回来。不这样做会很快导致奇怪的结果,例如,如果有多个函数定义变量x,应该使用哪个函数。如果变量的数量更大,您可以为它创建一个自定义的数据结构,例如将它们放入一个指定的列表中。

#3


1  

One could also use a function that redefines other functions in the specified environment.

还可以使用在指定环境中重新定义其他函数的函数。

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.

Here add_function_to_envir(my_function_name, to_envir) captures the script of the function, parses and reevaluates it in the new environment.

这里add_function_to_envir(my_function_name, to_envir)捕获函数的脚本,并在新环境中对其进行解析和重新计算。

Note: the name of the function for my_function_name needs to be in quotes.

注意:my_function_name函数的名称需要引用。