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

时间:2022-05-11 22:03:39

If I create a function as follows:

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

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

And call it with lapply: funs <- lapply(c('love', 'cherry'), what_is_love)

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

I get unexpected output:

我得到了意外的输出:

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

But note that this is not the case when you do not use lapply:

但请注意,当您不使用lapply时情况并非如此:

> f1 <- what_is_love('love')
> f2 <- what_is_love('cherry')
> f1()
f is love
> f2()
f is cherry

What gives?

是什么赋予了?

I know that funs <- lapply(c('love', 'cherry'), what_is_love) can be written out more fully:

我知道乐趣< - 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

But when I browse in, I see that both functions have their own environment:

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

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

But in each of those environments, f is the same...

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

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

I know the answer is "lazy evaluation", but I'm looking for a bit more depth... how does f end up re-assigned across both environments? Where does f come from? How does R lazy evaluation work under the hood in this example?

我知道答案是“懒惰评估”,但我正在寻找更深入的内容......如何在两种环境中重新分配? f来自哪里?在这个例子中,R懒惰的评估是如何工作的?

-

-

EDIT: I'm aware of the other question on lazy evaluation and functionals, but it just says the answer is "lazy evaluation" without explaining how the lazy evaluation actually works. I'm seeking greater depth.

编辑:我知道关于懒惰评估和功能的另一个问题,但它只是说答案是“懒惰评估”而没有解释懒惰评估实际上是如何工作的。我正在寻求更大的深度。

1 个解决方案

#1


11  

When you do

当你这样做

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

the inner function creates an enclosure for f, but the catch is that until you actually use a variable passed to a function, it remains a "promise" and is not actually evaluated. If you want to "capture" the current value of f, then you need to force the evaluation of the promise; you can use the force() function fo this.

内部函数为f创建了一个外壳,但是捕获的是直到你实际使用传递给函数的变量,它仍然是一个“承诺”,实际上并没有被评估。如果你想“捕获”f的当前值,那么你需要强制评估承诺;你可以使用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 

Without force(), f remains a promise inside both of the functions in your list. It is not evaluated until you call the function, and when you call the function that promise is evaluated to the last known value for f which is "cherry."

如果没有force(),f仍然是列表中两个函数内的一个promise。在调用函数之前,并且在调用函数时,不会对它进行求值,该函数将promise计算为f的最后已知值“cherry”。

As @MartinMorgran pointed out, this behavior has changed in R 3.2.0. From the release notes

正如@MartinMorgran指出的那样,这种行为在R 3.2.0中发生了变化。从发行说明

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.

诸如apply函数和Reduce()之类的高阶函数现在强制对它们应用的函数进行参数化,以消除惰性求值与闭包中的变量捕获之间的不良交互。这解决了PR#16093。

#1


11  

When you do

当你这样做

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

the inner function creates an enclosure for f, but the catch is that until you actually use a variable passed to a function, it remains a "promise" and is not actually evaluated. If you want to "capture" the current value of f, then you need to force the evaluation of the promise; you can use the force() function fo this.

内部函数为f创建了一个外壳,但是捕获的是直到你实际使用传递给函数的变量,它仍然是一个“承诺”,实际上并没有被评估。如果你想“捕获”f的当前值,那么你需要强制评估承诺;你可以使用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 

Without force(), f remains a promise inside both of the functions in your list. It is not evaluated until you call the function, and when you call the function that promise is evaluated to the last known value for f which is "cherry."

如果没有force(),f仍然是列表中两个函数内的一个promise。在调用函数之前,并且在调用函数时,不会对它进行求值,该函数将promise计算为f的最后已知值“cherry”。

As @MartinMorgran pointed out, this behavior has changed in R 3.2.0. From the release notes

正如@MartinMorgran指出的那样,这种行为在R 3.2.0中发生了变化。从发行说明

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.

诸如apply函数和Reduce()之类的高阶函数现在强制对它们应用的函数进行参数化,以消除惰性求值与闭包中的变量捕获之间的不良交互。这解决了PR#16093。