7

I am studying "environment" concept in R. Now I know emptyenv() is the parent of all environments and it has no parent, but is there a way to list all environments in current R session or say children of emptyenv()?

sm925
  • 2,648
  • 1
  • 16
  • 28
AAA
  • 695
  • 1
  • 7
  • 21
  • While `parent.env(e)` will return the parent environment of an environment, I don't think there is a reverse function. – r2evans Dec 26 '19 at 16:54
  • @r2evans this is an old one. I think we not need a reverse function to `parent.env`. Check out my answer. thanks – polkas Jan 31 '21 at 18:32

3 Answers3

1

My solution is looking for environments recursively. We could do it because of the fact that additional/new environments have to be create in a visible/known place. Still, there might be some lesser edge cases like environments inside functions or R6 classes:

fun <- function() {
  ee <- new.env()
  fun2 <-  function() {
    eee <- new.env()
  }
  return(1)
}

Another thing will be to grab e.g. environment of the function body, inside {}. It will be created during function evaluation not creation. This might be almost impossible.

I am demanding that the object recognized as an environment - is.environment and inherits(e, "environment"). The latter condition protecting us against objects like ggproto loaded with ggplot2.

set.seed(1234)

# New environments - x7
#######################
eval(quote(ee <- new.env(parent = baseenv())))

eval(quote(ee1 <- new.env(parent = baseenv())), envir = ee)

ee2 <- new.env()

ee3 <- new.env(parent = ee2)

eval(quote(ee4 <- new.env(parent = baseenv())), envir = ee3)

eval(quote(ee5 <- new.env()), envir = baseenv())

eval(quote(ee6 <- new.env(parent = .GlobalEnv)), envir = ee3)


#######################

subenv <- function(env) {
  envs <- unique(Filter(function(e) is.environment(e) && inherits(e, "environment"), lapply(ls(env), function(x) get(x, env))))
  unlist(append(envs, lapply(envs, subenv)))
}

search_all = function() {
  ees <- unlist(append(.GlobalEnv, rlang::env_parents(.GlobalEnv)))
  unname(unlist(lapply(ees, subenv)))
}

library(ggplot2) 

search_all()

results:

> search_all()
[[1]]
<environment: 0x7f9c75e66e10>

[[2]]
<environment: 0x7f9c75e65600>

[[3]]
<environment: 0x7f9c75e68e80>

[[4]]
<environment: 0x7f9c75e65e88>

[[5]]
<environment: 0x7f9c75e67ec0>

[[6]]
<environment: 0x7f9c75e6df38>

[[7]]
<environment: 0x7f9c75e6ef30>

EDIT:

This function might be used to maximize environments visibility.

new.env <- function(...) {
  ee <- base::new.env(...)
  if(!identical(parent.frame(), .GlobalEnv)) {
    assign(paste0("env_", data.table::address(ee)), ee, envir = .GlobalEnv)
    }
  ee
}

Unfortunately when some package is built it will be using base::new.env by default. Even assignInNamespace might not be satisfactory here.

polkas
  • 3,797
  • 1
  • 12
  • 25
1

Rather than look at all of the children of the empty environment, we can look at the parents of the global environment. This is an imperfect solution because some environments can exist is ways that are not parents of the global environment, but it's at least a partial solution.

The following recursive function will return a list of all environments exist as parents (grandparents, etc.) of the env argument. If you don't specify an environment, the global environment is used.

allParents = function(env = globalenv(), result = list()) {
  result = c(list(parent.env(env)), result)
  if(!identical(result[[1]], emptyenv())) {
    result <- allParents(result[[1]], result)
  }
  return(result)
}

allParents()

yields, for instance:

[[1]]
<environment: R_EmptyEnv>

[[2]]
<environment: base>

[[3]]
<environment: 0x55b78fe34790>
attr(,"name")
[1] "org:r-lib"

[[4]]
<environment: 0x55b78bc4dfe8>
attr(,"name")
[1] "Autoloads"

[[5]]
<environment: package:methods>
attr(,"name")
[1] "package:methods"
attr(,"path")
[1] "/usr/lib/R/library/methods"

[[6]]
<environment: package:datasets>
attr(,"name")
[1] "package:datasets"
attr(,"path")
[1] "/usr/lib/R/library/datasets"

[[7]]
<environment: package:utils>
attr(,"name")
[1] "package:utils"
attr(,"path")
[1] "/usr/lib/R/library/utils"

[[8]]
<environment: package:grDevices>
attr(,"name")
[1] "package:grDevices"
attr(,"path")
[1] "/usr/lib/R/library/grDevices"

[[9]]
<environment: package:graphics>
attr(,"name")
[1] "package:graphics"
attr(,"path")
[1] "/usr/lib/R/library/graphics"

[[10]]
<environment: package:stats>
attr(,"name")
[1] "package:stats"
attr(,"path")
[1] "/usr/lib/R/library/stats"

[[11]]
<environment: 0x55b78d81bd78>
attr(,"name")
[1] "tools:rstudio"
Geoffrey Poole
  • 1,102
  • 8
  • 10
0

The only way to do that would be to list all objects in the session, and subset to those which are environments, and that's impossible in R code.

You can list most objects by a recursive search through everything in the global environment, and the environments referred to by objects in it (every function and formula refer to an environment, for instance), and continue with its parent environment, etc. However, that won't get everything, because packages can create objects from within their C code that are not accessible directly from R code.

If you're willing to write tricky low-level C code, you would have to go through the same search in R's heap that the garbage collector does. The heap contains every object, but doesn't really distinguish between ones that have been deleted or not until garbage collection occurs. So you'd probably find some environments that aren't really accessible.

user2554330
  • 37,248
  • 4
  • 43
  • 90