[[<-
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:
<<-
?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
Regarding (2), the obvious solution is of course to use <<-
:
(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.
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.
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:
`[[<-` = 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)
}
The R-language definition (section 2.1.10) says:
Unlike most other R objects, environments are not copied when passed to functions or used in assignments.
Section "6.3 More on evaluation" also gives a slightly relevant hint:
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
get
),assign
to copy the modified list back into the original environment.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With