在非本地列表中替换值的赋值

时间:2022-06-08 12:06:18

[[<- behaves differently for lists and environments when used on non-local objects:

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

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

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

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

In other words, if the target of [[<- is an environment, it modifies the (nonlocal) environment, but if it’s a vector/list, it creates a new, local object.

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

I would like to know two things:

我想知道两件事:

  1. Why this difference in behaviour?
  2. 为什么会有这种行为上的差异?
  3. Is there a way of achieving the same result for lists as for environments, without using <<-?
  4. 是否有一种方法可以在不使用<<-的情况下,为列表和环境实现相同的结果?

Regarding (1), I’m aware of multiple differences between lists and environments (in particular, I know that environments don’t get copied) but the documentation does not mention why the semantics of [[<- differ between the two — in particular, why the operator would operate on different scope. Is this a bug? It’s counter-intuitive at least, and requires some non-trivial implementation shenanigans.1

关于(1),我知道列表和环境之间有很多不同之处(特别是,我知道环境不会被复制),但是文档中没有提到为什么[[<-]的语义在这两者之间存在差异——特别是为什么操作符会在不同的范围内操作。这是一个错误吗?至少这是违反直觉的,并且需要一些非平凡的实现诡计

Regarding (2), the obvious solution is of course to use <<-:

对于(2),显然的解决方案当然是使用<<-:

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

However, I prefer understanding the difference rigorously rather than just working around them. Furthermore, I’ve so far used assign instead of <<- and I prefer this, as it allows me greater control over the scope of the assignment (in particular since I can specify inherits = FALSE. <<- is too much voodoo for my taste.

然而,我更喜欢严格地理解差异,而不是仅仅围绕它们工作。此外,到目前为止,我使用了assign而不是<-我更喜欢这种方法,因为它允许我对赋值范围有更大的控制(特别是因为我可以指定inherits = FALSE)。<-对我的口味来说是太多的巫术了。

However, the above cannot be solved (as far as I know) using assign because assign only works on environments, not lists. In particular, while assign('x', 1, env) works (and does the same as above), assign('x', 1, lst) doesn’t work.

但是,使用assign不能解决上述问题(据我所知),因为assign只能在环境中工作,而不是在列表中。特别是,尽管assign('x', 1, env)可以工作(并与上面一样),assign('x', 1, lst)不能工作。


1 To elaborate, it’s of course expected that R does different thing for different object types using dynamic dispatch (e.g. via S3). However, this is not the case here (at least not directly): the distinction in scope resolution happens before the object type of the assignment target is known — otherwise the above would operate on the global lst, rather than creating a new local object. So internally [[<- has to do the equivalent of:

要详细说明,当然希望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)
}

1 个解决方案

#1


8  

The R-language definition (section 2.1.10) says:

R-language定义(2.1.10节)说:

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

与大多数其他R对象不同,环境在传递给函数或在赋值中使用时不会被复制。

Section "6.3 More on evaluation" also gives a slightly relevant hint:

“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.

当在列表中求值时,这也是正确的,但是原始列表不会改变,因为一个人实际上正在处理一个副本。

So, the answer to your first question is that lists need to be copied to assign into them, but environments can be modified in place (which has huge performance implications).

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

Regarding your second question:

关于你提到的第二个问题:

If you are working with a list, the only option seems to be to

如果你正在处理一个列表,唯一的选择似乎是

  • copy the list into the local scope (using get),
  • 将列表复制到本地范围(使用get),
  • assign into the list,
  • 分配到列表中,
  • use assign to copy the modified list back into the original environment.
  • 使用assign将修改后的列表复制回原始环境。

#1


8  

The R-language definition (section 2.1.10) says:

R-language定义(2.1.10节)说:

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

与大多数其他R对象不同,环境在传递给函数或在赋值中使用时不会被复制。

Section "6.3 More on evaluation" also gives a slightly relevant hint:

“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.

当在列表中求值时,这也是正确的,但是原始列表不会改变,因为一个人实际上正在处理一个副本。

So, the answer to your first question is that lists need to be copied to assign into them, but environments can be modified in place (which has huge performance implications).

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

Regarding your second question:

关于你提到的第二个问题:

If you are working with a list, the only option seems to be to

如果你正在处理一个列表,唯一的选择似乎是

  • copy the list into the local scope (using get),
  • 将列表复制到本地范围(使用get),
  • assign into the list,
  • 分配到列表中,
  • use assign to copy the modified list back into the original environment.
  • 使用assign将修改后的列表复制回原始环境。