How to dput a list with an environment?

162 views Asked by At

I am working on a function to basically grab arguments from inside a function. It works as expected, but when I try and transport the result, I have an envir in the list. It outputs fine via dput, but I CANNOT transport it.

> test(iris)
list(envir = <environment>, call = test(iris), fn.info = list(
    fn = "test", dot.keys = NULL, dot.vals = NULL, params = list(
        df = "iris"), map = list(df = "iris"), formals = list(
        df = "--EMPTY--")))

As you can see, the INTERNALS grab the fn function name, and function parameters. One element captured is the envir which is simple to reproduce as follows:

dput( (envir = environment()) );

The list at least TRAPPED the environment, but when I try and paste it back into a session, I get an error:

> x = list(envir = <environment>, call = test(iris), fn.info = list(
Error: unexpected '<' in "x = list(envir = <"
>     fn = "test", dot.keys = NULL, dot.vals = NULL, params = list(
Error: unexpected ',' in "    fn = "test","
>         df = "iris"), map = list(df = "iris"), formals = list(
Error: unexpected ')' in "        df = "iris")"
>         df = "--EMPTY--")))
Error: unexpected ')' in "        df = "--EMPTY--")"


Ideally, I would like to pair of functions that would cast it to/from envir as a string, so I can transport it.

Question: How to transport an environment via dput?

env.toString(envir) {}

env.fromString(envir) {}

The above represents my current roadmap, but I am open to suggestions. I have tried as.character , as.raw, as.hexmode... I believe deparse internally will work, but that will not enable me to recover the environment as an object without the propose env.fromString

3

There are 3 answers

0
Andrew Brown On

Perhaps you could use ls() to get the names of all objects in the environment, and then put their names and values in a list, then reconstruct an environment from that list with list2env()... Something like:

x <- new.env()
x$foo <- 1:3

xnames <- ls(envir = x)
xlist <- lapply(xnames, function(y) {
  z <- x[[y]]
  if (!inherits(z, 'environment'))
    return(z)
})
names(xlist) <- xnames

dput(list(my_env = xlist))
#> list(my_env = list(foo = 1:3))

a_list <- list(my_env = list(foo = 1:3))

x2 <- list2env(a_list$my_env)
print(x2$foo)
#> [1] 1 2 3
0
Andrew Brown On

If you just need to remove the items from a list that are an environment you can just iterate over the list remove the elements that inherit from 'environment'

x <- list(a = new.env(), b = "foo")
dput(x[-sapply(x, inherits, 'environment')])
#> list(b = "foo")
3
moodymudskipper On

As you've noted, dput() doesn't provide the code necessary to rebuild an environment

env1 <- new.env()
env1$x <- "something"
env2 <- asNamespace("utils")
env3 <- .GlobalEnv
test <- list(env1, env2, env3, 1, "a") 
dput(test)
#> list(<environment>, <environment>, <environment>, 1, "a")

An essential property of environments is they have a parent, so to reproduce an environment you might have to reproduce its parent, and the parent of its parent etc. Thus it is not possible (or reasonable ?) to tackle the general case.

Some environments however already have a name in R, or are easy to call, using asNamespace(), it would be reasonable to expect dput() to do it but it doesn't.

The {constructive} package does though and can provide a "sometimes good enough" solution in the more general case by using as.environment() on lists.

constructive::construct(test, check = FALSE)
#> list(as.environment(list(x = "something")), asNamespace("utils"), .GlobalEnv, 1, "a")

The argument check = FALSE is to prevent it to fail since the object that would be recreated through the code is not strictly identical to the input.

It will soon be on CRAN but meanwhile : https://github.com/cynkra/constructive


pragmatically you might try :

env.toString <- function (envir) {
  sprintf("as.environment(%s)", paste(deparse(as.list(envir)), collapse = "\n"))
}