R中的惰性评估

lazy evaluation in R

我在 R 中有以下代码:

named_list = list()
for (i in 1:5){
named_list[[i]] = function(one,two){c(one,two, i)}
}

然而,当我调用函数时:

> named_list[[1]]("first", "second")
[1] "first"  "second" "5"

有没有办法在不使用应用函数的情况下使其正常工作(到 return "first"、"second"、“1”)?我已经尝试使用另一个线程中推荐的 force 函数,但我无法让它工作。

谢谢。

编辑:为了澄清,我希望制作一个函数列表,每个函数都包含该函数在该列表中位置的索引。特别要注意

> named_list[[1]]("first", "second")
[1] "first"  "second" "5"

> named_list[[2]]("first", "second")
[1] "first"  "second" "5"

> named_list[[3]]("first", "second")
[1] "first"  "second" "5"

> named_list[[4]]("first", "second")
[1] "first"  "second" "5"

> named_list[[5]]("first", "second")
[1] "first"  "second" "5"

这显然不是我们想要的行为。问题是循环 i 通过 1 到 5,R 看到第一个 'i' 索引 named_list,但没有看到第二个 'i',它在我试图定义的函数中.

我知道以下是一个可能的解决方案(虽然我不知道为什么它有效):

named_list = lapply(1:5, function(i) function(one,two)(c(one,two,i)))

但我想知道是否有使用 for 循环的替代解决方案。

每当我遇到这种情况时,我决定将其写成文本并将其包装在 eval 语句中。像这样。

named_list = list()
for (i in 1:5){

  eval(parse(text = paste0("named_list[[i]] = function(one,two){c(one,two,", i, ")}")))

}

named_list[[1]]("first", "second")

现在我明白了

> named_list[[1]]("first", "second")
[1] "first"  "second" "1" 

随意。

所以我所做的就是让我知道我想在文本中加入字符串并让它以这种方式评估它。

可能有更好的解决方案,但这会为您完成工作。

我认为您的问题与 scopenamespace 有关。即,当在一个函数中引用了一个尚未在该函数中本地定义的变量时,R 开始在 parent "frame"(定义其变量的环境)中搜索;如果不存在,则转到 parent 的 parent 帧(grand-parent 帧?);等等(一个好的读物是 Advanced R: Environments; an extra read might be the same book's chapter on Memory。)

在任何给定时间查看 environment 是 used/searched 是有帮助的。我将重点关注当前的 parent,以及在函数内部时的 "grand-parent" 环境;但是要意识到,深层嵌套的函数可能有更多(这表明您在依赖 R 到 hunt-down 并找到不在本地环境中的变量的特定实例时需要非常小心!)。

注意:您很可能不会得到相同的 <environment: 0x000...> 指示。这些引用是完全不可复制的,并且每次此代码为 运行.

时都会更改

让我们从有效的 lapply 设置开始 :

print(environment())
# <environment: R_GlobalEnv>
nl1 <- lapply(1:2, function(i) {
  e1 <- environment()
  str(list(where="inside lapply", env=e1, parent=parent.env(e1)))
  function(one,two) {
    e2 <- environment()
    str(list(where="inside func", env=e2, parent=parent.env(e2),
             grandparent=parent.env(parent.env(e2))))
    c(one, two, i)
  }
})
# List of 3
#  $ where : chr "inside lapply"
#  $ env   :<environment: 0x0000000009128fe0> 
#  $ parent:<environment: R_GlobalEnv> 
# List of 3
#  $ where : chr "inside lapply"
#  $ env   :<environment: 0x00000000090bb578> 
#  $ parent:<environment: R_GlobalEnv> 

首先请注意,在 lapply 内的每次迭代中,都会有一个新环境,从 9128fe0 开始,其 parent 是全局环境。在 lapply 的第二次迭代中,我们处于 90bb578,并且在该环境中,我们定义了 function(one,two),其本地环境为 8f811b8(我们将在下一个代码块)。

意识到此时,R 还没有尝试解析 i。让我们运行一个函数:

nl1[[2]](11,12)
# List of 4
#  $ where      : chr "inside func"
#  $ env        :<environment: 0x0000000008f811b8> 
#  $ parent     :<environment: 0x00000000090bb578> 
#  $ grandparent:<environment: R_GlobalEnv> 
# [1] 11 12  2

所以当我们引用 i 时,R 按顺序搜索以下内容:

  • 8f811b8:在 function(one,two)... 内,未找到
  • 90bb578: 直接 parent env, inside function(i) ...; 找到
  • R_GlobalEnv(没有搜索,因为之前找到了)

好的,让我们试试 for 循环:

nl2 <- list()
for (i in 1:2) {
  e1 <- environment()
  str(list(where="inside for", env=e1, parent=parent.env(e1)))
  nl2[[i]] <- function(one,two) {
    e2 <- environment()
    str(list(where="inside func", env=e2, parent=parent.env(e2),
             grandparent=parent.env(parent.env(e2))))
    c(one, two, i)
  }
}
# List of 3
#  $ where : chr "inside for"
#  $ env   :<environment: R_GlobalEnv> 
#  $ parent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"
# List of 3
#  $ where : chr "inside for"
#  $ env   :<environment: R_GlobalEnv> 
#  $ parent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"

首先要注意的是,在for循环的每次迭代中,本地环境是R_GlobalEnv,这应该是有道理的。 (您可以安全地忽略对 tcltk 环境的引用作为 parent。)

好的,现在当我们进行 nl2[[1]] 调用时,请注意 parent 环境是(也许现在,这并不奇怪)R_GlobalEnv 环境:

nl2[[1]](11,12)
# List of 4
#  $ where      : chr "inside func"
#  $ env        :<environment: 0x000000001b1a6720> 
#  $ parent     :<environment: R_GlobalEnv> 
#  $ grandparent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"
# [1] 11 12  2

这是 R 第一次需要 查找 i,因此它首先在 1b1a6720 内搜索(在 function(one,two) 内,其中未找到),然后在 R_GlobalEnv.

那为什么 return "2"?

因为R_GlobalEnvi的值是我们调用nl2[[2]]for循环中i的最后一个值.看到这个:

rm(i)
for (i in 1:100) { } # no-op
i
# [1] 100

更能说明问题的是,如果我们现在尝试调用该函数:

nl2[[1]](11,12)
# List of 4
#  $ where      : chr "inside func"
#  $ env        :<environment: 0x000000000712c2a0> 
#  $ parent     :<environment: R_GlobalEnv> 
#  $ grandparent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"
# [1]  11  12 100

因此在该函数中 i 的计算是惰性的,因为它会在您调用该函数时进行搜索。

在您的环境中(在您更改任何代码之前),如果您输入 i <- 100,您会看到类似的行为。


如果您绝对反对使用 lapply(这是我在这里的首选方法,即使我在这里不了解您的基本需求),请尝试明确定义您的函数周围的环境。一种方法是使用 local,这将在现有 parent 环境中保留搜索,同时允许我们 "force" 我们想要使用的 i。 (存在其他选项,我邀请其他人发表评论并让您更多地探索环境,因为它 m

nl3 <- list()
for (i in 1:2) {
  e1 <- environment()
  str(list(where="inside for", env=e1, parent=parent.env(e1)))
  nl3[[i]] <- local({
    i <- i # forces it locally within this env
    function(one,two) {
      e2 <- environment()
      str(list(where="inside func", env=e2, parent=parent.env(e2),
               grandparent=parent.env(parent.env(e2))))
      c(one, two, i)
    }
  })
}
# List of 3
#  $ where : chr "inside for"
#  $ env   :<environment: R_GlobalEnv> 
#  $ parent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"
# List of 3
#  $ where : chr "inside for"
#  $ env   :<environment: R_GlobalEnv> 
#  $ parent:<environment: package:tcltk> 
#   ..- attr(*, "name")= chr "package:tcltk"
#   ..- attr(*, "path")= chr "c:/R/R-3.3.3/library/tcltk"
nl3[[1]](11,12)
# List of 4
#  $ where      : chr "inside func"
#  $ env        :<environment: 0x0000000019ca23e0> 
#  $ parent     :<environment: 0x000000001aabe388> 
#  $ grandparent:<environment: R_GlobalEnv> 
# [1] 11 12  1
i <- 1000
nl3[[1]](11,12)
# List of 4
#  $ where      : chr "inside func"
#  $ env        :<environment: 0x0000000008d0bc78> 
#  $ parent     :<environment: 0x000000001aabe388> 
#  $ grandparent:<environment: R_GlobalEnv> 
# [1] 11 12  1

(您可能会注意到每次调用函数时的本地环境都会发生变化,而 parent 则不会。这是因为当您调用函数时,它从函数调用的开头开始一个新环境。你 "know" 并依赖它,因为你假设在你的函数开始时,没有定义任何变量。这是正常的。)