为什么包装函数不能按预期工作?

3

以下是四个函数,后者包装了前者。

a <- 0

f1 <- function(expr) {
  a1 <- 1
  eval(expr)
}

f2 <- function(expr) {
  a2 <- 2
  f1(expr)
}

f3 <- function(expr) {
  a3 <- 3
  f2(expr)
}

f4 <- function(expr) {
  a4 <- 4
  f3(expr)
}

请按照以下实验步骤进行操作:

> f4(a)
0

这段代码看起来是没问题的。但是如果我们调用

f4(a4) 出现错误:Error in eval(expr) : object 'a4' not found

> f4(a3)
Error in eval(expr) : object 'a3' not found

...

> f2(a2)
Error in eval(expr) : object 'a2' not found

> f2(a1)
Error in eval(expr) : object 'a1' not found

> f1(a1)
Error in eval(expr) : object 'a1' not found

我检查每个函数体的本地环境和父级环境。f3的父级帧是f4的本地环境,...,而f1的父级是f2的函数体。这种情况清楚吗?如何解决此问题以使函数调用允许后续函数(例如f3)查找已定义的符号(例如a4)?
1个回答

8
我强烈建议您花些时间阅读 Advanced R:Environments
首先,当我运行 f1(a1) 时,我也会得到“找不到对象'a1'”,而不是像您上面得到的“1”。
问题在于,默认情况下,R使用函数的封闭环境来解析变量。函数的封闭环境是在定义函数时确定的,而不是在调用函数时确定的。因此,它不会沿着调用链去解析变量名。您可以使用 parent.frame() 环境明确查看父调用,但这些环境在嵌套函数调用中不会链接在一起。
get() 循环遍历封闭父环境以查找变量类似,您可以编写自己的函数来遍历调用环境并查看哪些变量可用。
call.get <- function(val) {
    for(i in 1:sys.nframe()) {
        if (exists(val, envir=sys.frame(i), inherits=F)) {
            return(get(val, envir=sys.frame(i)))
        }
    }
    return(NULL)
}

call.ls <- function(val) {
    vars<-lapply(1:sys.nframe(), function(i) ls(envir=parent.frame(i)))
    return(sort(unique(unlist(vars))))
}

然后如果你做了一些像这样的事情

f1 <- function(expr) {
  a1 <- 1
  call.ls()
}

f2 <- function(expr) {
  a2 <- 2
  f1(expr)
}

f3 <- function(expr) {
  a3 <- 3
  f2(expr)
}

f4 <- function(expr) {
  a4 <- 4
  f3(expr)
}

f4(1)

你将得到

"a1"   "a2"   "a3"   "expr" "FUN"  "val"  "X"  

并且你可以使用

call.get("a3")

从父调用帧中获取这些变量之一是您需要解决的问题之一。 但是,您面临的另一个问题是在调用子函数时触发了表达式参数的评估。当您执行以下操作时:
f2 <- function(expr) {
  a2 <- 2
  f1(expr)
}

这段代码在f2环境中评估expr并将结果传递给f1,但此时您将失去评估。通过使用"..."可以最简单地通过惰性评估。类似于:

f1 <- function(...) {
    a1 <- 1
    expr<-deparse(substitute(...))
    call.get(expr)
}
f2 <- function(...) {
    a2 <- 2
    f1(...)
}
f2(a1)
# [1] 1
f2(a2)
# [1] 2

否则,您需要使用do.call更明确地传递表达式。
f1 <- function(expr) {
    a1 <- 1
    expr<-deparse(substitute(expr))
    call.get(expr)
}
f2 <- function(expr) {
    expr<-substitute(expr)
    a2 <- 2
    do.call(f1, list(expr))
}

f2(a1)
# [1] 1
f2(a2)
# [1] 2

感谢您详细的解释!我想我应该再读一遍那一章。 - Kun Ren
2
@KunRen 如果您发现这个回答解决了您的问题,应该点击答案旁边的复选标记以接受它并关闭问题。 - MrFlick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接