In R, running the expression x <- 1
defines a variable x
in the global environment with the value 1
. Doing the same within a function defines the variable within the function's environment instead.
Using rlang::with_env
, we can also do the same thing with an arbitrary environment:
e <- new.env()
rlang::with_env(e, {
x <- 1
y <- 2
f <- function(x) print(x)
g <- function() f(1)
})
e$x
#> [1] 1
e$g()
#> [1] 1
Created on 2021-10-26 by the reprex package (v2.0.1)
However, I can't figure out how to do the same in a function. That is, a function which receives expressions and then runs them in a blank environment, returning the environment:
set_in_env <- function(expr) {
e <- new.env()
# q <- rlang::enquo(expr)
# z <- quote(expr)
# rlang::with_env(e, substitute(expr))
# rlang::with_env(e, parse(text = substitute(expr)))
# rlang::with_env(e, q)
# rlang::with_env(e, rlang::eval_tidy(q))
# rlang::with_env(e, z)
# rlang::with_env(e, eval(z))
rlang::with_env(e, expr)
rlang::with_env(e, {x <- 1})
return(e)
}
e <- set_in_env({y <- 2})
rlang::env_print(e)
#> <environment: 0000000014678340>
#> parent: <environment: 0000000014678730>
#> bindings:
#> * x: <dbl> <-- ONLY `x` WAS SET, NOT `y`!
That is, the function is given the expression y <- 2
which should be run in a new environment. For demonstration purposes, the function also internally sets x <- 1
in the environment.
No matter what I've tried, the environment is only created with e$x
, never defining e$y <- 2
(the commented out code were other failed attempts).
I'm confident this can be done and that I'm just missing something. So, can someone give me a hand?
CodePudding user response:
It's odd that the with_env
function doesn't seem to allow for injecting expressions into the expression parameter. Here's a work around
set_in_env <- function(expr) {
e <- new.env()
q <- rlang::enexpr(expr)
rlang::inject(rlang::with_env(e, !!q))
rlang::with_env(e, {x <- 1})
return(e)
}
We explicltly use rlang::inject
to inject the expression to the call and then inject
will also evaluate it.
CodePudding user response:
This could be a base
solution:
set_in_env <- function(expr) {
e <- new.env()
# Resolve the given 'expr'ession as a 'call', before evaluating that call in the
# environment 'e'. Otherwise, 'expr' will first be evaluated within 'set_in_env()',
# with such consequences as described below.
eval(expr = substitute(expr), envir = e)
# ^^^^ ^^^^^^^^^^
# 'eval()' with 'substitute()'
# Avoid evaluating anything whatsoever about the 'x <- 1' assignment, until doing so
# in the environment 'e'. Otherwise, 'x <- 1' will first be evaluated within
# 'set_in_env()', and 'x' will be available in 'set_in_env()' yet unavailable in the
# environment 'e'.
evalq(expr = {x <- 1}, envir = e)
# ^^^^^
# 'evalq()' on its own
return(e)
}
When we put set_in_env()
through its paces as in your question
e <- set_in_env({y <- 2})
rlang::env_print(e)
we get the desired results:
<environment: 0000013E34E1E0D0>
parent: <environment: 0000013E34E1E488>
bindings:
* x: <dbl>
* y: <dbl>