【问题标题】:Explain a lazy evaluation quirk解释一个懒惰的评估怪癖
【发布时间】:2013-04-14 08:08:28
【问题描述】:

我正在阅读 Hadley Wickhams 在 Github 上的书,特别是 this part on lazy evaluation。在那里,他在add/adders 函数的部分中给出了惰性求值后果的示例。让我引用这段话:

在使用 lapply 或循环创建闭包时,这个 [惰性求值] 很重要:

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

x 在您第一次调用加法器时被懒惰地评估 职能。至此,循环完成,最终值为 x 是 10。因此所有的加法器函数都会将 10 加到它们的 输入,可能不是你想要的!手动强制评估修复 问题:

add <- function(x) {
  force(x)
  function(y) x + y
}
adders2 <- lapply(1:10, add)
adders2[[1]](10)
adders2[[10]](10)

我似乎不明白那一点,那里的解释很少。有人可以详细说明那个特定的例子,并解释那里发生了什么吗?我对“此时,循环完成,x的最终值为10”这句话感到特别困惑。什么循环?什么最终值,在哪里?一定是我想念的简单的东西,但我只是看不到它。提前非常感谢。

【问题讨论】:

  • 请注意,自 R 3.2.0 起,此问题的答案已更改,请参阅下面的答案。
  • 对@jhin 评论的补充:虽然lapply() 在最近的R 中发生了变化,但打算在lapply() 所在的任何地方使用的函数purrr::map() 仍然表现得像旧的lapply()相对于闭包的共享环境。但是,我不会指望purrr::map() 的这种“时代错误”会继续存在,因为它可能会在未来的版本中得到纠正。
  • @jhin 实际上,我猜 hadley 的教程是直接从 github 构建的,所以在 R 3.2.0 之后阅读它现在很奇怪,因为该版本使该教程中关于惰性评估的整个部分变得毫无意义:没有与addersadders2 的输出有更大的不同!

标签: r lazy-evaluation


【解决方案1】:

从 R 3.2.0 开始,这不再适用!

change log 中的对应行内容为:

高阶函数,例如 apply 函数和 Reduce() 现在 对它们应用的函数强制参数以消除 惰性求值和变量捕获之间的不良交互 在闭包中。

确实:

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

【讨论】:

    【解决方案2】:

    目标:

    adders <- lapply(1:10, function(x)  add(x) )
    

    是创建add 函数的列表,第一个将 1 添加到其输入,第二个添加 2,等等。延迟评估导致 R 等待真正创建加法器函数,直到您真正开始调用函数。问题是在创建第一个加法器函数后,x 增加了lapply 循环,以 10 的值结束。当您调用第一个加法器函数时,惰性求值现在构建函数,得到 @ 的值987654325@。问题是原来的x不再等于1,而是lapply循环结束时的值,即10。

    因此,惰性求值会导致所有加法器函数等到 lapply 循环完成后才真正构建函数。然后他们使用相同的值(即 10)构建函数。Hadley 建议的解决方案是强制直接评估 x,避免延迟评估,并使用正确的 x 值获取正确的函数。

    【讨论】:

    • 好的,让我重新表述一下,看看我是否做对了。当我们调用lapply 时,R 会记住所有 10 个加法器函数的结构,但还没有计算 x。当我们调用第一个加法器函数时,R 说,啊哈,让我们看看它是什么,从 lapply 调用中获取 x,此时它已经是 10,并将第一个调用的加法器函数计算为 10 + y。其余的加法器函数也一样,使它们都相同。可能粗略地说,但这是它的逻辑吗?
    • 我相信是这样的。
    • @hadley 当我调用第一个加法器函数时,lapply 循环已经结束。加法器函数究竟在哪里寻找 x?为什么 x = 10 的值仍然存在?
    • 惰性求值实际上是如何工作的?所有十个不同的加法器函数都有十个独立的环境,其中包含x。我想也许他们在被评估之前都指向某个地方,但指向哪里?父环境中没有x
    • 第一次调用该函数时创建环境。在 lapply 循环完成后,x 变量此时等于 10。所以它们都是一样的。
    猜你喜欢
    • 1970-01-01
    • 2019-01-07
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2015-09-06
    • 2016-12-14
    • 2021-12-25
    相关资源
    最近更新 更多