R 解释一个懒惰的评估怪癖

R 解释一个懒惰的评估怪癖,r,lazy-evaluation,R,Lazy Evaluation,我正在读哈德利·威克汉姆关于Github的书,尤其是。在那里,他给出了一个延迟计算结果的例子,在加法/加法器函数部分。让我引用这一点: 当使用lappy或循环创建闭包时,此[延迟评估]非常重要: add <- function(x) { function(y) x + y } adders <- lapply(1:10, add) adders[[1]](10) adders[[10]](10) 当您第一次调用一个加法器时,x被惰性地计算 功能。此时,循环已完成,并且 x是10

我正在读哈德利·威克汉姆关于Github的书,尤其是。在那里,他给出了一个延迟计算结果的例子,在加法/加法器函数部分。让我引用这一点:

当使用lappy或循环创建闭包时,此[延迟评估]非常重要:

add <- function(x) {
  function(y) x + y
}
adders <- lapply(1:10, add)
adders[[1]](10)
adders[[10]](10)
当您第一次调用一个加法器时,x被惰性地计算 功能。此时,循环已完成,并且 x是10。因此,所有加法器函数都将在其 输入,可能不是你想要的!手动强制执行评估修复 问题是:

add <- function(x) {
  force(x)
  function(y) x + y
}
adders2 <- lapply(1:10, add)
adders2[[1]](10)
adders2[[10]](10)
我似乎不明白这一点,而且解释也很少。请有人详细说明一下这个例子,并解释一下那里发生了什么?在这一点上,我对这个句子感到特别困惑,循环是完整的,x的最终值是10。什么环路?什么是最终价值,在哪里?一定是我错过了一些简单的东西,但我就是看不到。提前非常感谢。

目标是:

adders <- lapply(1:10, function(x)  add(x) )
就是创建一个add函数列表,第一个函数向其输入中添加1,第二个函数向其输入中添加2,等等。延迟求值会导致R等待真正创建adder函数,直到您真正开始调用这些函数。问题是,在创建第一个加法器函数后,x会随着lappy循环的增加而增加,最终值为10。当您调用第一个加法器函数时,惰性求值现在构建该函数,得到x的值。问题是原始x不再等于1,而是等于Lappy循环末尾的值,即10


因此,延迟求值会导致所有加法器函数等到lappy循环完成后才真正构建函数。然后,它们以相同的值构建它们的函数,即10。Hadley建议的解决方案是强制直接计算x,避免延迟计算,并使用正确的x值获得正确的函数。

这在R3.2.0中不再适用

表中的对应行为:

高阶函数,如apply函数和Reduce now 将参数强制应用于它们应用的函数,以消除 惰性计算和变量捕获之间的不良交互 闭关自守

事实上:

add <- function(x) {
  function(y) x + y
}
adders <- lapply(1:10, add)
adders[[1]](10)
# [1] 11
adders[[10]](10)
# [1] 20

好吧,让我重新表述一下,看看我是否做对了。当我们调用lappy时,R类会记住所有10个加法器函数的结构,但不会计算x。当我们调用第一个加法器函数时,R说,啊哈,让我们看看这是什么,从lappy调用中取x,在该点上已经是10,并将第一个被调用的加法器函数计算为10+y。对于其余的加法器函数也一样,使它们都相同。“也许说得很粗略,但这就是它的逻辑吗?我相信是这样的。”哈德利当我调用第一个加法器函数时,拉普拉循环已经结束了。加法器函数在哪里查找x?为什么x=10的值会持续存在?惰性评估实际上是如何工作的?所有十个不同的加法器函数都有十个单独的环境,其中包含x。我想他们可能都指向评估之前的某个地方,但指向哪里?父环境中没有x。该环境是在首次调用函数时创建的。在lappy循环完成后,此时x变量等于10。因此,它们都是相同的。请注意,这个问题的答案在R3.2.0之后发生了变化,请参见下面我的答案。补充@jhin的评论:虽然Lappy在最近的R中发生了变化,但函数purr::map(打算在Lappy所在的任何地方使用)在闭包共享环境中的行为仍然与旧的Lappy类似。然而,我不会指望purrr::map的这种“时代错误”会继续存在,因为它可能会在未来的版本中得到纠正。@jhin实际上,我猜hadley的教程是直接从github构建的,所以在R3.2.0之后阅读它现在是相当奇怪的,因为该版本使得该教程中关于惰性评估的整个部分都变得毫无意义:加法器和加法器2的输出不再有区别了!