分配以替换非本地列表中的值

Assignment to replace value in nonlocal list

[[<- 用于非本地对象时,列表和环境的行为不同:

lst = list()
env = new.env()

(function () lst[['x']] = 1)()
(function () env[['x']] = 1)()
lst
# list()

as.list(env)
# $x
# [1] 1

换句话说,如果 [[<- 的目标是环境,它会修改(非本地)环境,但如果它是 vector/list,它会创建一个新的本地对象。

我想知道两件事:

  1. 为什么会出现这种行为差异?
  2. 有没有一种方法可以让列表获得与环境相同的结果,不用 使用<<-?

关于 (1),我知道列表和环境之间存在多种差异(特别是,我知道环境不会被复制)但文档不会提及为什么 [[<- 的语义在两者之间不同——特别是,为什么运算符会在不同的范围内运行。这是一个错误吗?这至少是违反直觉的,并且需要一些不平凡的实施恶作剧。1

关于(2),显而易见的解决方案当然是使用<<-:

(function () lst[['x']] <<- 1)()

但是,我更喜欢严格地理解差异,而不是仅仅解决它们。此外,到目前为止,我使用 assign 而不是 <<-,我更喜欢这个,因为它允许我更好地控制分配的范围(特别是因为我可以指定 inherits = FALSE<<- 太不符合我的口味了。

但是,使用 assign 无法解决上述问题(据我所知),因为 assign 仅适用于环境,不适用于列表。特别是,虽然 assign('x', 1, env) 有效(并且与上述相同),但 assign('x', 1, lst) 无效。


1 详细来说,当然期望 R 使用动态分派(例如通过 S3)为不同的对象类型做不同的事情。然而,不是这里的情况(至少不是直接的):范围解析的区别发生在赋值目标的对象类型已知之前——否则上面的操作将在全局上运行lst,而不是创建一个新的本地对象。所以在内部 [[<- 必须做相当于:

`[[<-` = function (x, i, value) {
    if (exists(x, mode = 'environment', inherits = TRUE))
        assign(i, value, pos = x, inherits = FALSE)
    else if (exists(x, inherits = FALSE)
        internal_assign(x, i, value)
    else
        assign(x, list(i = value), pos = parent.frame(), inherits = FALSE)
}

R-language definition(第 2.1.10 节)说:

Unlike most other R objects, environments are not copied when passed to functions or used in assignments.

“6.3 更多评估”部分也给出了一个稍微相关的提示:

Notice that evaluation in a given environment may actually change that environment, most obviously in cases involving the assignment operator, such as

eval(quote(total <- 0), environment(robert$balance)) # rob Rob

This is also true when evaluating in lists, but the original list does not change because one is really working on a copy.

因此,第一个问题的答案是需要复制列表以分配给它们,但可以就地修改环境(这对性能有巨大影响)。

关于你的第二个问题:

如果您正在使用列表,唯一的选择似乎是

  • 将列表复制到本地范围(使用 get),
  • 分配到列表中,
  • 使用assign将修改后的列表复制回原来的环境。