R中的惰性求值

cetgtptt  于 2023-06-27  发布在  其他
关注(0)|答案(2)|浏览(109)

在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"

有没有一种方法可以让它正常工作(返回“first”,“second”,“1”)而不使用apply函数?我已经尝试使用力功能的建议,在另一个线程,但我不能让它工作。
谢谢
编辑:为了澄清一些问题,我希望制作一个函数列表,每个函数都包含该函数在该列表中的位置的索引。特别是,请注意

> 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"

这显然不是期望的行为。问题是,通过1到5循环i,R看到第一个'i'索引named_list,但看不到第二个'i',它在我试图定义的函数中。
我知道以下是一个可能的解决方案(尽管我不知道 * 为什么 * 它有效):

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

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

gzszwxb4

gzszwxb41#

我认为您的问题与 scopenamespace 有关。也就是说,当在一个函数中引用了一个没有在该函数中本地定义的变量时,R开始在父“框架”(定义其变量的环境)中搜索;如果不存在,则它转到父的父帧(祖-父帧?);等等。(一个很好的阅读是Advanced R: Environments;额外的阅读可能是同一本书关于Memory的章节。
查看在任何给定时间正在使用/搜索的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>

首先要注意,lapply中的每次迭代都有一个新的环境,从9128fe0开始,其父环境是全局env。在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:直接父环境,在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的引用。)
好了,现在当我们到达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

这是R第一次需要 findi,所以它首先在1b1a6720中搜索(在function(one,two)中,没有找到它),然后在R_GlobalEnv中搜索。

为什么返回“2”?

因为在我们调用nl2[[2]]时,R_GlobalEnvi的值是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,它将保留在现有父环境中的搜索,同时允许我们“强制”使用我们想要使用的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

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

db2dz4w8

db2dz4w82#

每当我遇到这种情况时,我决定将其作为文本写出来,并将其 Package 在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"

如你所愿。
所以我所做的就是让我知道我想要的字符串在文本中,并让它以这种方式计算它。
可能有更好的解决方案,但这将为您做的工作。

相关问题