当组件可能分散在环境中时评估通话

问题描述

我有一个要计算的表达式expr;我需要评估的符号/值对可能在三种环境中的一种(或多种!)中,我不确定是哪一种。我想找到一种方便,有效的方式来“链接”环境。 有没有一种安全的方法可以避免复制环境内容

这是设置:

env1 <- list2env(list(a=1))
env2 <- list2env(list(b=1))
env3 <- list2env(list(c=1))
expr <- quote(a+b)

因此,我需要在环境exprenv1组合中评估env2(但我不一定知道)。 eval(expr,env1)中的任何一个eval(expr,env2);和eval(expr,env3)将失败,因为这些环境中没有一个包含所有必需的符号。

假设我愿意假设符号是env1 + env2中的 env1 +中的 env3。我可以:

  1. 按照this question中所述为每个配对创建组合环境。

问题

  • 如果我使用一种涉及创建新环境的解决方案,而我的环境中有很多东西,那可能会很昂贵。
  • 使用parent.env()<-可能不是一个好主意-如?parent.env中所述:

替换函数parent.env

(尽管根据资料来源的历史,关于“在不久的将来”被移除的警告至少已使用19年了……)

(事实上,我已经设法使用这种方法来引发一些无限循环)

  1. 使用
tryCatch(eval(call,envir=as.list(expr1),enclos=expr2),error=function(e) {
             tryCatch(eval(call,as.list(expr1),enclos=expr3))

创建“环境中的环境”;一次尝试组合的一对,看看哪个可行。请注意,enclos= 仅在envir是列表或配对列表时有效,这就是为什么我必须使用as.list()的原因。

问题:我认为我仍然最终将expr1内容复制到新环境中。

我可以使用更深层嵌套的tryCatch()子句集合来一次尝试使用环境,然后再复制它们,这将有助于避免在不必要的地方复制(但看起来很笨拙)。

解决方法

将环境转换为列表,将它们连接起来,并将其用作eval的第二个参数。请注意,这不会修改环境本身。

L <- do.call("c",lapply(list(env1,env2,env3),as.list))
eval(expr,L)
## [1] 2

还请注意,这不会复制abc的内容。它们仍然位于原始地址:

library(pryr)

with(env1,address(a))
## [1] "0x2029f810"

with(L,address(a))
## [1] "0x2029f810"
,

不,没有简单的方法可以链接环境。如您所知,每个环境都有一个父环境,即另一个环境,因此整个环境形成一个树形结构。 (树的根是空的环境。)您不能轻易地从树上摘下一片叶子并将其嫁接到另一片叶子上,而无需对其结构进行更改。

因此,如果您真的需要以描述的方式评估表达式,则将需要对其进行解析,自己查找名称并将值替换为表达式。但这甚至不一定一定会在最后为您带来相同的价值,因为substitute()和类似的功能可能会涉及其中。

我的建议是重新开始,不要尝试像您在说的那样做一个表达。这可能涉及复制,但是请记住,在R中复制通常很便宜:仅当您修改其中一个副本时,成本才会出现。

编辑后添加:

当前其他四个答案隐含地假设env1env3与您的示例一样简单。如果是这样,那么我将使用@ G.Grothendieck的解决方案。但是所有人都无法在您的示例中进行这种简单的改动:

env1 <- list2env(list(a=1))
env2parent <- list2env(list(b=1))
env2 <- new.env(parent = env2parent)
env3 <- list2env(list(c=1))
expr <- quote(a+b)

我可以使用quote(b)来评估eval(quote(b),envir = env2),但是不能使用其他解决方案来评估expr,除非在通过的环境列表中也包括了env2parent

再次编辑:

这里的解决方案基本上可以执行我建议的操作,除了解析之外,它使用@ r2evans答案之一中的all.vars函数。它通过将所有变量复制到一个公共环境中来工作,因此会进行复制,但名称会保留:

envfunc3 <- function(expr,...) {
  vars <- all.vars(expr)
  env <- new.env()
  for (v in vars) {
    for (e in list(...))
      if (exists(v,envir = e)) {
        assign(v,get(v,envir = e),envir = env)
        break
      }
  }
  eval(expr,envir=env)
}
,

另一种策略:临时重建父级环境链,使用R的自然搜索顺序,然后将其改回。

我认识到“将来”的提法,并且您不鼓励使用parent.env,但是...它仍然非常有效。我相信使用它的大多数“风险”(因此,在文档中令人沮丧的评论)是,更改而不是不更改会引入很多方法来破坏它们。我确实在这里看到了一些脆弱性,因为我对expr的假设是它相对“简单”。如果存在(例如)依赖于C库的活动绑定,则可能会引起问题。

尽管如此...

envfunc <- function(expr,...) {
  envs <- list(...)
  if (length(envs) > 1) {
    parents <- lapply(envs,parent.env)
    on.exit({
      for (i in seq_along(envs)) parent.env(envs[[i]]) <- parents[[i]]
    },add = TRUE)
    for (i in seq_along(envs)[-1]) parent.env(envs[[i]]) <- envs[[i-1]]
  }
  eval(expr,envir = envs[[ length(envs) ]])
}

str(list(env1,env3))
# List of 3
#  $ :<environment: 0x0000000099932bc8> 
#  $ :<environment: 0x0000000099931d58> 
#  $ :<environment: 0x00000000445b97c0> 
str(lapply(list(env1,parent.env))
# List of 3
#  $ :<environment: 0x000000000787d7a8> 
#  $ :<environment: 0x000000000787d7a8> 
#  $ :<environment: 0x000000000787d7a8> 
str(lapply(list(env1,function(e) lapply(e,address)))
# List of 3
#  $ :List of 1
#   ..$ a: chr "00000000bb23c350"
#  $ :List of 1
#   ..$ b: chr "00000000bb23c1c8"
#  $ :List of 1
#   ..$ c: chr "00000000bb23c040"

envfunc(expr,env1,env3)
# [1] 2

str(list(env1,address)))
# List of 3
#  $ :List of 1
#   ..$ a: chr "00000000bb23c350"
#  $ :List of 1
#   ..$ b: chr "00000000bb23c1c8"
#  $ :List of 1
#   ..$ c: chr "00000000bb23c040"

这有效地产生了环境的链接列表,这意味着环境的顺序提供了事项。在此示例中,没有重复,但是不难想象它会产生影响。

envfunc(expr,env3)
# [1] 2
env1$b <- 99
envfunc(expr,env3)
# [1] 2
env3$b <- 99
envfunc(expr,env3)
# [1] 100
,

另一个选项,与另一个完全不同:主动绑定?我可能会继续努力...

envfunc2 <- function(expr,...) {
  vars <- all.vars(expr)
  env <- environment()
  for (e in list(...)) {
    vars_in_e <- intersect(vars,names(e))
    vars <- setdiff(vars,vars_in_e)
    for (v in vars_in_e) makeActiveBinding(v,local({ v=v; e=e; function() get(v,envir = e); }),env)
  }
  eval(expr)
}

envfunc2(expr,env3)
# [1] 2

这包括实时get从其各自环境中设置值的开销。

,

我认为attach()确实是您想要的,为什么它是最令人讨厌的R函数与您的情况无关,我们可以构建一个仔细的包装器来分离所有on.exit,应该安全:

eval_with_envs <- function(expr,...) {
  dots <- substitute(...())
  on.exit(
    for (env in dots) {
      if(as.character(env) %in% search())
        eval.parent(bquote(detach(.(env))))
    }
  )
  for (env in dots) {
    eval.parent(bquote(attach(.(env))))
  }
  eval.parent(expr)
}

eval_with_envs(expr,env3)
#> [1] 2