如何运行一个环境中的任意表达式,将所有结果存储在环境中?

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

在 R 中,运行使用表达式 x <- 1 在全局环境中定义了一个变量 x,其值为 1。在函数中执行相同操作会在函数环境中定义变量。

使用rlang::with_env,我们也可以在任意环境下做同样的事情:

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

reprex package (v2.0.1)

于 2021-10-26 创建

但是,我不知道如何在函数中执行相同的操作。也就是说,一个函数接收表达式,然后 运行 在空白环境中发送它们,返回环境:

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`!

也就是说,函数被赋予表达式y <- 2,在新环境中应该是运行。出于演示目的,该函数还在环境中内部设置 x <- 1

无论我尝试过什么,环境都是用e$x创建的,从未定义e$y <- 2(注释掉的代码是其他失败的尝试)。

我相信这是可以做到的,我只是遗漏了一些东西。那么,有人可以帮助我吗?

奇怪的是 with_env 函数似乎不允许将表达式注入到表达式参数中。这是解决方法

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)
}

我们明确地使用 rlang::inject 将表达式注入到调用中,然后 inject 也会对其求值。

这可能是 base 解决方案:

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)
}

当我们按照您的问题 set_in_env() 进行测试时

e <- set_in_env({y <- 2})
  
rlang::env_print(e)

我们得到了想要的结果:

<environment: 0000013E34E1E0D0>
parent: <environment: 0000013E34E1E488>
bindings:
 * x: <dbl>
 * y: <dbl>

1) 我们可以这样使用 eval/substitute:

f <- function(expr) {
  eval(substitute({
     expr
     x <- 1
  }), e <- new.env())
  e
}

# test
e <- f( {y <- 2} )
ls(e)
## [1] "x" "y"

2) 或者我们可以像这样在 f 中重用 environment/frame:

f <- function(expr) {
  eval(substitute({
     expr
     x <- 1
   }))
   rm(expr)
   environment()
}

e <- f( {y <- 2} )
ls(e)
## [1] "x" "y"

我将其作为 issue in the rlang GitHub 提出,其中包括其他评论(包括他打算弃用 with_env)@lionel 给出了一种非常简洁的方法:

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

reprex package (v2.0.1)

于 2021-10-27 创建

我实际上已经尝试 eval_tidy 使用 quosures,我需要的是 eval_bare 使用表达式。