Reputation: 843
I would like to ask if it is possible to copy/move all the objects of one environment to another, at once. For example:
f1 <- function() {
print(v1)
print(v2)
}
f2 <- function() {
v1 <- 1
v2 <- 2
# environment(f1)$v1 <- v1 # It works
# environment(f1)$v2 <- v2 # It works
environment(f1) <- environment(f2) # It does not work
}
f2()
f1()
Upvotes: 26
Views: 12286
Reputation: 47340
The other current solutions who actually attempt to make a copy will all fail if the environment contains promises, because they convert environments to lists.
{rlang} has now env_clone()
which will clone the environment, including promises and active bindings.
It doesn't do a deep copy though, and clones the environment by default with a new parent, you can do rlang::env_clone(env, parent.env(env))
to keep the same parent though.
I recommend the above, find below a previous solution from when {rlang} had more limitations. It should behave similar but provides an option for deep copy, with caveats given at the bottom.
The solution below works in these cases. Following @geoffrey-poole 's idea I propose an argument to deep copy or not, and I showcase the function on a test case.
It uses unexported functions from the package {pryr}. I don't know of a base R equivalent.
The function
clone_env <- function(env, deep = FALSE) {
is_unevaled_promise <- function(name, env) {
pryr:::is_promise2(name, env) && !pryr:::promise_evaled(name, env)
}
# create new environment with same parent
clone <- new.env(parent = parent.env(env))
for(obj in ls(env, all.names = TRUE)) {
promise_lgl <- is_unevaled_promise(as.symbol(obj), env = env)
if(promise_lgl) {
# fetch promise expression and env
promise_expr <- pryr:::promise_code(obj, env)
promise_env <- pryr:::promise_env(obj, env)
# Assign this expression as a promise (delayed assignment) in our
# cloned environment
eval(bquote(
delayedAssign(obj, .(promise_expr), eval.env = promise_env, assign.env = clone)))
} else {
obj_val <- get(obj, envir = env)
if(is.environment(obj_val) && deep) {
assign(obj, clone_env(obj_val, deep = TRUE),envir= clone)
} else {
assign(obj, obj_val, envir= clone)
}
}
}
attributes(clone) <- attributes(env)
clone
}
Shallow copy
Let's build an environment containing a character variable, a promise (note that a
is undefined), and a nested environment.
create_test_env <- function(x = a){
y <- "original"
nested_env <- new.env()
nested_env$nested_value <- "original"
environment()
}
env <- create_test_env()
ls(env)
#> [1] "nested_env" "x" "y"
# clone it, with deep = FALSE
shallow_clone <- clone_env(env, deep = FALSE)
#> Registered S3 method overwritten by 'pryr':
#> method from
#> print.bytes Rcpp
ls(shallow_clone)
#> [1] "nested_env" "x" "y"
# the promise was copied smoothly
a <- 42
shallow_clone$x
#> [1] 42
# We can change values independently
shallow_clone$y <- "modified"
env$y
#> [1] "original"
# except if we have nested environents!
shallow_clone$nested_env$nested_value <- "modified"
env$nested_env$nested_value
#> [1] "modified"
Deep copy
Let's do it all over again, but with a deep clone now, we see the nested values are distinct this time.
env <- create_test_env()
deep_clone <- clone_env(env, deep = TRUE)
a <- 42
deep_clone$x
#> [1] 42
deep_clone$y <- "modified"
env$y
#> [1] "original"
deep_clone$nested_env$nested_value <- "modified"
env$nested_env$nested_value
#> [1] "original"
Created on 2020-09-10 by the reprex package (v0.3.0)
Note that the deep copy is not super robust:
.GlobalEnv
, .BaseNamespaceEnv
etc there, do we really want to clone those ? We could have an argument to feed arguments not to cloneI haven't met the need to go further in the rabbit hole. I you need this feel free to edit or copy and improve into your own answer.
Upvotes: 10
Reputation: 1298
The "clone" method posted by Tommy won't make a true (deep) clone when e1
contains names that reference other environments. For instance, if e1$nestedEnv
references an environment, e2$nestedEnv
will reference the same environment, not a copy of that environment. Thus, the name e1$nestedEnv$bar
will reference the same memory location as e2$nestedEnv$bar
and any new value assigned to e1$nestedEnv$bar
will be reflected for e2$nestedEnv$bar
as well. This may be desirable behavior, but calling e2
a clone of e1
could be misleading.
Here is a function that will allow the user to make either a copy of an environment while also copying any nested environments (a "deep clone", using deep = TRUE
), or just use the method proposed by Tommy to copy the environment while maintaining original references to any nested environments (using deep = FALSE
).
The 'deep = TRUE' method uses rapply
to recursively call cloneEnv
on nested environment within envir
, for as many levels as the environments are nested. So, in the end, it recursively calls rapply
, which is a bit of a mind bender, but works pretty well.
Note that if a nested environment contains a name that references a parent environment, using the "deep" method will never return from the recursive calls. If I could have figured out a way to check for this, I would have included it...
Note, too, that environments can have attributes, so copying the attributes would be necessary for a true clone, which this solution also addresses.
cloneEnv <- function(envir, deep = T) {
if(deep) {
clone <- list2env(rapply(as.list(envir, all.names = TRUE), cloneEnv, classes = "environment", how = "replace"), parent = parent.env(envir))
} else {
clone <- list2env(as.list(envir, all.names = TRUE), parent = parent.env(envir))
}
attributes(clone) <- attributes(envir)
return(clone)
}
An example:
Create environment e1
, which also contains a nested environment:
e1 <- new.env()
e1$foo <- "Christmas"
e1$nestedEnv <- new.env()
e1$nestedEnv$bar <- "New Years"
Show the values for foo
and bar
:
e1$foo
[1] "Christmas"
e1$nestedEnv$bar
[1] "New Years"
Make a deep clone (i.e. e2
contains makes a copy of nestedEnv
)
e2 <- cloneEnv(e1, deep = TRUE)
nestedEnv
in e1
references a difference environment than nestedEnv
in e2
:
identical(e1$nestedEnv, e2$nestedEnv)
[1] FALSE
But the values are the same because e2$nestedEnv
is a copy of e1$nestedEnv
:
e2$foo
[1] "Christmas"
e2$nestedEnv$bar
[1] "New Years"
Change the values in e2
:
e2$foo <- "Halloween"
e2$nestedEnv$bar <- "Thanksgiving"
And values in e1
remain unchanged, again, because e1$nestedEnv
points to a different environment than e2$nestedEnv
:
e1$foo
[1] "Christmas"
e2$foo
[1] "Halloween"
e1$nestedEnv$bar
[1] "New Years"
e2$nestedEnv$bar
[1] "Thanksgiving"
Now, re-create e1
using Tommy's method:
e2 <- cloneEnv(e1, deep = FALSE)
nestedEnv
in e2
points to the same environment as nestedEnv
in e1
:
identical(e1$nestedEnv, e2$nestedEnv)
[1] TRUE
Update the values in e2
and e2
's nestedEnv
:
e2$foo <- "Halloween"
e2$nestedEnv$bar <- "Thanksgiving"
Values of foo
are independent:
e1$foo
[1] "Christmas"
e2$foo
[1] "Halloween"
but updating value e2
's bar
has also updated e1
's bar
because e1$nestedEnv
and e2$nestedEnv
reference (point to) the same environment.
e1$nestedEnv$bar
[1] "Thanksgiving"
e2$nestedEnv$bar
[1] "Thanksgiving"
Upvotes: 6
Reputation: 9687
I use this function in my package to copy objects:
copyEnv <- function(from, to, names=ls(from, all.names=TRUE)) {
mapply(assign, names, mget(names, from), list(to),
SIMPLIFY = FALSE, USE.NAMES = FALSE)
invisible(NULL)
}
Upvotes: 3
Reputation: 19
To do it:
environment(f1) <- environment(f2) # It does not work
Open the f1
environment and run do this:
ls(load(f2))
Upvotes: 0
Reputation: 40861
There seem to be at least 3 different things you can do:
To clone:
# Make the source env
e1 <- new.env()
e1$foo <- 1
e1$.bar <- 2 # a hidden name
ls(e1) # only shows "foo"
# This will clone e1
e2 <- as.environment(as.list(e1, all.names=TRUE))
# Check it...
identical(e1, e2) # FALSE
e2$foo
e2$.bar
To copy the content, you can do what @gsk showed. But again, the all.names
flag is useful:
# e1 is source env, e2 is dest env
for(n in ls(e1, all.names=TRUE)) assign(n, get(n, e1), e2)
To share the environment is what @koshke did. This is probably often much more useful. The result is the same as if creating a local function:
f2 <- function() {
v1 <- 1
v2 <- 2
# This local function has access to v1 and v2
flocal <- function() {
print(v1)
print(v2)
}
return(flocal)
}
f1 <- f2()
f1() # prints 1 and 2
Upvotes: 33
Reputation: 72769
You could use assign:
f1 <- function() {
print(v1)
print(v2)
}
f2 <- function() {
v1 <- 1
v2 <- 2
for(obj in c("v1","v2")) {
assign(obj,get(obj),envir=f1.env)
}
}
If you don't want to list out the objects, ls()
takes an environment argument.
And you'll have to figure out how to get f1.env to be an environment pointing inside f1 :-)
Upvotes: 5
Reputation: 66902
Try this:
f2 <- function() {
v1 <- 1
v2 <- 2
environment(f1) <<- environment()
}
Upvotes: 9