R 中的惰性求值

1 lazy-loading r

我在 R 中有以下代码:

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

但是,当我调用该函数时:

> named_list[[1]]("first", "second")
[1] "first"  "second" "5"
Run Code Online (Sandbox Code Playgroud)

有没有办法让它正常工作(返回“第一”,“第二”,“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"
Run Code Online (Sandbox Code Playgroud)

这显然不是期望的行为。问题是,将 i 循环到 1 到 5,R 会看到第一个“i”索引named_list,但看不到我试图定义的函数内部的第二个“i”。

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

named_list = lapply(1:5, function(i) function(one,two)(c(one,two,i)))
Run Code Online (Sandbox Code Playgroud)

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

r2e*_*ans 5

我认为您的问题与范围名称空间有关。也就是说,当在一个函数中引用了一个尚未在该函数中本地定义的变量时,R开始在父“框架”(定义其变量的环境)中搜索;如果不存在,则转到父级的父级框架(祖级父级框架?);(对此,一本不错的读物是《高级 R:环境》;额外的读物可能是同一本书中有关“内存”的章节。)

查看environment在任何给定时间的使用/搜索情况会很有帮助。我将重点关注当前环境、父环境以及在函数内部时的“祖父母”环境;不过,要意识到深度嵌套的函数可能有更多(这表明您在依赖 R 来寻找并查找不在本地环境中的变量的特定实例时需要非常小心!)。

注意:您很可能不会得到相同的<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> 
Run Code Online (Sandbox Code Playgroud)

首先请注意,在 中的每次迭代中lapply,都会有一个新环境,从 开始9128fe0,其父环境是全局环境。在 的第二次迭代中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
Run Code Online (Sandbox Code Playgroud)

因此,当我们引用 时i,R 会按顺序搜索以下内容来找到它:

  • 8f811b8: 里面function(one,two)...没找到
  • 90bb578:直接父环境,内部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"
Run Code Online (Sandbox Code Playgroud)

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

好的,现在当我们接到电话时nl2[[1]],请注意父环境是(也许现在,并不奇怪)环境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
Run Code Online (Sandbox Code Playgroud)

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

那么为什么它返回“2”呢?

因为在我们调用 时,iin的值是循环中的最后一个值。看看这个:R_GlobalEnvnl2[[2]]ifor

rm(i)
for (i in 1:100) { } # no-op
i
# [1] 100
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

因此,该函数内的评估i是惰性的,因为它在调用该函数时进行搜索。

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


如果您绝对反对使用lapply(这是我在这里的首选方法,即使我不理解您的潜在需求),请尝试显式定义函数周围的环境。一种方法是使用local,它将保留在现有父环境中的搜索,同时允许我们“强制”i我们想要使用的内容。(存在其他选项,我邀请其他人发表评论,并让您更多地探索环境。)

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
Run Code Online (Sandbox Code Playgroud)

(您可能会注意到,每次调用函数时,本地环境都会发生变化,而父函数则不会。这是因为当您调用函数时,它会在函数调用开始时以新环境开始。您“知道”并且依赖于此,因为您假设在函数的开头没有定义任何变量。这是正常的。)