curve3d 找不到局部函数 "fn"

curve3d can't find local function "fn"

我正在尝试使用 emdbook 包中的 curve3d 函数来创建在另一个函数内局部定义的函数的等值线图,如以下最小示例所示:

library(emdbook)
testcurve3d <- function(a) {
  fn <- function(x,y) {
    x*y*a
  }
  curve3d(fn(x,y))
}

没想到,这会产生错误

> testcurve3d(2)
 Error in fn(x, y) : could not find function "fn" 

而同样的想法适用于 base-package 的更基本的 curve 功能:

testcurve <- function(a) {
  fn <- function(x) {
    x*a
  }
  curve(a*x)
}
testcurve(2)

问题是如何重写 curve3d 以使其按预期运行。

eval - parse 解决方案绕过了对变量范围的一些担忧。这直接传递变量和函数的值,而不是传递变量或函数名称。

library(emdbook)

testcurve3d <- function(a) {
  fn <- eval(parse(text = paste0(
    "function(x, y) {",
    "x*y*", a,
    "}"
  )))

  eval(parse(text = paste0(
    "curve3d(", deparse(fn)[3], ")"
    )))
}

testcurve3d(2)

我找到了其他我不太喜欢的解决方案,但也许它会对你有所帮助。

您可以创建函数 fn how a call object 并在 curve3d 中对其求值:

fn <- quote((function(x, y) {x*y*a})(x, y))
eval(call("curve3d", fn))

另一个函数内部,存在连续问题,a必须在全局环境中,但可以用substitute修复。

示例:

testcurve3d <- function(a) {
  fn <- substitute((function(x, y) {
                      c <- cos(a*pi*x)
                      s <- sin(a*pi*y/3)
                      return(c + s)
                      })(x, y), list(a = a))
  eval(call("curve3d", fn, zlab = "fn"))
}

par(mfrow = c(1, 2))
testcurve3d(2)
testcurve3d(5)

您可以暂时attach将功能环境添加到搜索路径以使其工作:

testcurve3d <- function(a) {
  fn <- function(x,y) {
    x*y*a
  }
  e <- environment()
  attach(e)
  curve3d(fn(x,y))
  detach(e)
}

分析

问题出在curve3d中的这一行:

eval(expr, envir = env, enclos = parent.frame(2))

此时,我们似乎有10帧深度,fn定义在parent.frame(8)中。因此,您可以编辑 curve3d 中的行来使用它,但我不确定它有多稳健。也许 parent.frame(sys.nframe()-2) 可能更健壮,但正如 ?sys.parent 警告的那样,可能会发生一些奇怪的事情:

Strictly, sys.parent and parent.frame refer to the context of the parent interpreted function. So internal functions (which may or may not set contexts and so may or may not appear on the call stack) may not be counted, and S3 methods can also do surprising things.

Beware of the effect of lazy evaluation: these two functions look at the call stack at the time they are evaluated, not at the time they are called. Passing calls to them as function arguments is unlikely to be a good idea.