Reputation: 2867
I would like to evaluate some code in the environment which will have
the access to libraries (all environments above .GlobalEnv
) but won't have
access to the objects created in the .GlobalEnv
.
I've tried couple solutions but none seemed to work as expected
Here environment created in the .GlobalEnv
have an access to the other
objects in the .GlobalEnv
.
.GlobalEnv$ee <- environment()
eval(
parse(text = "library(dplyr);mutate(iris, x = 1)"),
envir = .GlobalEnv$ee
)
var_in_global <- "x"
eval(
expr = parse(text = "ls()"),
envir = .GlobalEnv$ee
) # expect empty
eval(
expr = parse(text = "print(var_in_global)"),
envir = ee
) # expect error
This one is Coded different way but result is th same as 1st
ee <- new.env(parent = globalenv())
eval(
parse(text = "library(dplyr);mutate(iris, x = 1)"),
envir = ee
)
var_in_global <- "x"
eval(
expr = parse(text = "ls()"),
envir = ee
)
eval(
expr = parse(text = "print(var_in_global)"),
envir = ee
)
In this case environment doesn't have an access to the .GlobalEnv
objects
but libraries loaded after will be attached below, so the environment doesn't
have an access to these libraries as well.
ee <- new.env(parent = parent.env(globalenv()))
eval(
parse(text = "library(dplyr);mutate(iris, x = 1)"),
envir = ee
)
var_in_global <- "x"
eval(
expr = parse(text = "ls()"),
envir = ee
)
eval(
expr = parse(text = "print(var_in_global)"),
envir = ee
)
Using answer from @Allan Cameron, I've tried to make this code working. And library(dplyr);mutate(...)
were correctly evaluated in new environment.
ee <- new.env(parent = parent.env(globalenv()))
eval(
parse(text = "library <- function(...) base::library(..., pos = 3)"),
envir = ee
)
eval(
parse(text = "library(dplyr);mutate(iris, x = 1)"),
envir = ee
)
var_in_global <- "x"
eval(
expr = parse(text = "ls()"),
envir = ee
)
eval(
expr = parse(text = "print(var_in_global)"),
envir = ee
)
However, problem is much deeper. Some packages have more dependencies which are loaded along. Consider this example where I've replaced library(dplyr);mutate()
with library(Hmisc);impute(...)
. This example fails - couldn't find impute
function (which is wrong)
ee <- new.env(parent = parent.env(globalenv()))
eval(
parse(text = "library <- function(...) base::library(..., pos = 3)"),
envir = ee
)
eval(
parse(text = "library(Hmisc);impute(iris[,1], 1)"),
envir = ee
) # expect to work
Do you have any ideas how to make an environment which will be "parallel" node to the global, and still have libraries attached before?
Upvotes: 3
Views: 411
Reputation: 173803
The problem is that when you call library
, by default it attaches the package at pos = 2
in the search path:
pos
the position on the search list at which to attach the loaded namespace. Can also be the name of a position on the current search list as given by search().
So, when I start an R session and do search()
, I get:
#> [1] ".GlobalEnv" "tools:rstudio" "package:stats"
#> [4] "package:graphics" "package:grDevices" "package:utils"
#> [7] "package:datasets" "package:methods" "Autoloads"
#> [10] "package:base"
And when I call library(dplyr)
then repeat search()
, I get:
#> [1] ".GlobalEnv" "package:dplyr" "tools:rstudio"
#> [4] "package:stats" "package:graphics" "package:grDevices"
#> [7] "package:utils" "package:datasets" "package:methods"
#> [10] "Autoloads" "package:base"
So, if you make ee
have the same parent as the Global environment before attaching any packages, you will have the problem that the packages are placed between the Global Environment and ee
's entry into the search path.
There are a few ways round this, but perhaps the simplest is to start a new R session and define:
library <- function(...) base::library(..., pos = 3)
Which ensures that packages loaded in the Global Workspace are always placed after ee
's "entry point" to the search path. This produces the desired behaviour:
ee <- new.env(parent = parent.env(globalenv()))
var_in_global <- "x"
eval(expr = parse(text = "ls()"), envir = ee)
#> character(0)
eval(expr = parse(text = "print(var_in_global)"), envir = ee)
#> Error in print(var_in_global): object 'var_in_global' not found
library(dplyr) # Note this is called in the global environment
eval(parse(text = "head(mutate(iris, n = 1), 5)"), envir = ee)
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species n
#> 1 5.1 3.5 1.4 0.2 setosa 1
#> 2 4.9 3.0 1.4 0.2 setosa 1
#> 3 4.7 3.2 1.3 0.2 setosa 1
#> 4 4.6 3.1 1.5 0.2 setosa 1
#> 5 5.0 3.6 1.4 0.2 setosa 1
Note that as a (possibly desirable) side effect, since the modified library
function is defined in the global environment, then if library
is called from inside ee
, it will be base::library
that is dispatched, and any packages loaded within ee
will therefore only be accessible to ee
.
EDIT
If you want code called within ee
to affect the global search path as well as ee
's search path, you could try:
ee <- new.env(parent = parent.env(globalenv()))
ee$library <- function(...) {
mc <- match.call()
mc[[1]] <- quote(base::library)
eval(mc, envir = globalenv())
this_env <- parent.frame()
if(!identical(this_env, globalenv()))
parent.env(this_env) <- parent.env(globalenv())
}
This gives us:
eval(
parse(text = "library(Hmisc);impute(iris[,1], 1)"),
envir = ee
)
#> [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8 4.3 5.8 5.7 5.4 5.1 5.7
#> [20] 5.1 5.4 5.1 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0 5.5 4.9
#> [39] 4.4 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4 6.9 5.5 6.5 5.7 6.3
#> [58] 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7 5.6 5.8 6.2 5.6 5.9 6.1 6.3 6.1 6.4 6.6
#> [77] 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3 5.6 5.5 5.5 6.1 5.8 5.0 5.6
#> [96] 5.7 5.7 6.2 5.1 5.7 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2 6.5 6.4 6.8 5.7
#> [115] 5.8 6.4 6.5 7.7 7.7 6.0 6.9 5.6 7.7 6.3 6.7 7.2 6.2 6.1 6.4 7.2 7.4 7.9 6.4
#> [134] 6.3 6.1 7.7 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8 6.7 6.7 6.3 6.5 6.2 5.9
However, we need to be careful. If library
is called in the global environment, this will not update the search path for ee
. So we would need to have a global function like:
library <- function(...) {
base::library(...)
parent.env(ee) <- parent.env(globalenv())
}
Of course, it would be much nicer to have your own package do this. That way, you can have a single library
function which tests its calling frame and dispatches the appropriate method, without having these spare library
functions floating around the workspaces.
Created on 2020-09-15 by the reprex package (v0.3.0)
Upvotes: 4