为什么 R 无法通过词法范围找到参数的值?

Why can R not find the value of an argument through lexical scoping?

我一直在阅读 Hadley Wickham 的 Advanced R,以便更好地了解 R 的机制及其背后的工作原理。到目前为止,我很喜欢它,一切都很清楚。有一个问题占据了我的脑海,我还没有找到解释。 我非常熟悉 R 的范围规则,它决定了如何将值分配给自由变量。然而,我一直在努力解决为什么 R 在第一种情况下无法通过词法范围找到形式参数的值的问题。考虑以下示例:

y <- 4
f1 <- function(x = 2, y) {
  x*2 + y
}

f1(x = 3)

它通常会抛出错误,因为我没有为参数 y 分配默认值。但是,如果我在函数体中创建一个局部变量 y,它不会抛出任何错误: 我还在 Professeur Matloff 的书中读到,参数就像局部变量一样,所以这就是为什么这个问题对我来说仍然是个谜。

f1 <- function(x = 2, y) {
  y <- 4
  x*2 + y
}

f1(x = 3)

这里也没有错误,原因很清楚:

y <- 2
f2 <- function(x = 2) {
  x*2 + y
}

f2()

非常感谢您。

注意R只会在你去使用变量的时候抛出错误。如果你有

f1 <- function(x = 2, y) {
  x*2 + 5
}

f1(x = 3)
# [1] 11

一切都会好起来的。那是因为该参数是一个“承诺”,在您实际使用它之前不会得到解决。这使您可以执行

之类的操作
f1 <- function(x = 2, y=x+5) {
  x*2 + y
}

f1(x = 3)
# [1] 14

其中 y 值实际上将使用 x 的值,该值在评估 promise 时传递给函数。此外你还可以做

f1 <- function(x = 2, y=z+2) {
  z <- x + 10
  x*2 + y
}

f1(x = 3)
[1] 21

其中 y 能够获取调用函数时甚至不存在的 z 的值。同样,这是因为参数值是承诺的,并且仅在实际使用时才进行评估。他们在评估时可以访问环境中的所有值。但请注意,这只有效,因为默认参数值是在函数体的上下文中计算的。这与将值传递给函数时不同。在那种情况下,值是在调用环境中计算的,而不是本地函数体。所以你做不到

f1(x = 3, y=z+2)
# Error in f1(x = 3, y = z + 2) : object 'z' not found

您在第一个函数中出现错误的原因是当您尝试在 x*2 + y 中使用它时 y 的值不存在。由于您已将 y 定义为参数,因此它不再是“自由”变量,并且不会在父作用域中查找。您不会在第二个函数中遇到错误,因为您已将 y 变量重新绑定到局部函数变量,因此您根本不会使用参数值。

如果你运行

f1 <- function(x = 2, y) {
  y <- 4
  x*2 + y
}

f1(x = 3, y=200)
# [1] 10

2000基本没了。重新分配 y 后,您将无法再访问该值。 R 在重新定义之前不会检查变量是否已经存在,因此没有任何东西会尝试评估函数参数的承诺值 y

一旦对 promise 进行了评估,参数将像局部变量一样起作用。

我认为 R 在这里非常一致。 只要不调用 y,R 就不会理会这个叫做 promise 的东西。

f1 <- function(x = 2, y) {
  x*2 + 5
}

f1(x = 3)
# [1] 11

在您的第一个示例中,y 不能从全局环境派生,因为 y 是在函数环境内部本地定义的。由于缺少该值,将引发错误

y <- 4
f2 <- function(x = 2, y) {
  x*2 + y
}

f2(x = 3)
#> Error in f2(x = 3) : argument "y" is missing, with no default

这可能会导致类似这样的情况,即“隐藏”容易出错的设置,直到用户不小心偶然发现它。

f3 <- function(x = 2, y) {
  if (x >= 2) {
    y <- 4
  }
  x * 2 + y
}

f3(x = 2)
#> 8

这种情况很难用单元测试来覆盖,这就是为什么你经常看到函数体顶部带有 force() 的原因。

f4 <- function(x = 2, y) {
  force(y)
  
  if (x > 2) {
    y <- 4
  }
  x * 2 + y
}

f4(x = 2)
#> Error in force(y) : argument "y" is missing, with no default

有一个不错的 blog post 相当广泛地涵盖了惰性求值。