rrlangnser-environment

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


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?


Solution

  • I raised this as an issue in the rlang GitHub, where among other comments (including that he intends to deprecate with_env) @lionel gave a very clean way of doing this:

    library(rlang)
    
    set_in_env <- function(expr) {
      e <- env()
      
      expr <- rlang::enexpr(expr)
      
      rlang::eval_bare(expr, e)
      
      return(e)
    }
    
    e <- set_in_env({y <- 2})
    e$y
    #> [1] 2
    

    Created on 2021-10-27 by the reprex package (v2.0.1)

    I'd actually tried eval_tidy with quosures, what I needed was eval_bare with expressions.