您能否更清楚地解释 R 函数运算符中的惰性求值?

Can you more clearly explain lazy evaluation in R function operators?

如果我创建一个函数如下:

what_is_love <- function(f) {
  function(...) {
    cat('f is', f, '\n')
  }
}

并用 lapply 调用它:funs <- lapply(c('love', 'cherry'), what_is_love)

我得到了意外的输出:

> funs[[1]]()
f is cherry
> funs[[2]]()
f is cherry

但请注意,当您不使用 lapply:

时,情况并非如此
> f1 <- what_is_love('love')
> f2 <- what_is_love('cherry')
> f1()
f is love
> f2()
f is cherry

什么给了?

我知道funs <- lapply(c('love', 'cherry'), what_is_love)可以更完整的写出来:

params <- c('love', 'cherry')
out <- vector('list', length(params))
for (i in seq_along(params)) {
  out[[i]] <- what_is_love(params[[i]])
}
out

但是当我浏览时,我看到这两个函数都有自己的环境:

Browse[1]> out[[1]]
function(...) {
    cat('f is', f, '\n')
  }
<environment: 0x109508478>
Browse[1]> out[[2]]
function(...) {
    cat('f is', f, '\n')
  }
<environment: 0x1094ff750>

但是在每个环境中,f 都是一样的...

Browse[1]> environment(out[[1]])$f
[1] "cherry"
Browse[1]> environment(out[[2]])$f
[1] "cherry"

我知道答案是 "lazy evaluation",但我正在寻找更深入的内容... f 如何最终在两个环境中重新分配? f 从哪里来?在此示例中,R 惰性求值在幕后是如何工作的?

-

编辑: 我知道 the other question 关于惰性求值和泛函,但它只是说答案是 "lazy evaluation" 而没有解释惰性如何评估确实有效。我正在寻求更大的深度。

当你

what_is_love <- function(f) {
  function(...) {
    cat('f is', f, '\n')
  }
}

内部函数为 f 创建了一个封装,但要注意的是,在您实际 使用 传递给函数的变量之前,它仍然是 "promise" 并没有实际评估。如果你想"capture"当前值为f,那么你需要对promise强制求值;您可以使用 force() 函数。

what_is_love <- function(f) {
  force(f)
  function(...) {
    cat('f is', f, '\n')
  }
}
funs <- lapply(c('love', 'cherry'), what_is_love)

funs[[1]]()
# f is love 
funs[[2]]()
# f is cherry 

如果没有 force()f 仍然是列表中两个函数中的承诺。在您调用该函数之前不会对其进行评估,并且当您调用该函数时,promise 被评估为 f 的最后一个已知值,即 "cherry."

正如@MartinMorgran 指出的那样,此行为在 R 3.2.0 中已更改。来自 release notes

Higher order functions such as the apply functions and Reduce() now force arguments to the functions they apply in order to eliminate undesirable interactions between lazy evaluation and variable capture in closures. This resolves PR#16093.