[[<-
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
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)
}