Home > Mobile >  How to run an arbitrary expression in an environment, storing all results in the environment?
How to run an arbitrary expression in an environment, storing all results in the environment?

Time:10-27

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>
  • Related