[[<-
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:
我想知道两件事:
- Why this difference in behaviour?
- 为什么会有这种行为上的差异?
- Is there a way of achieving the same result for lists as for environments, without using
<<-
? - 是否有一种方法可以在不使用<<-的情况下,为列表和环境实现相同的结果?
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将修改后的列表复制回原始环境。