R:替换绑定在所有父环境中的变量

R: Substitute variables bound in all parent environments

函数 base::substitute(expr, env),根据其文档页面,

returns the parse tree for the (unevaluated) expression expr, substituting any variables bound in env.

我正在寻找一种方法来替换不是在一个特定环境中绑定的任何变量,而是在当前调用堆栈中的所有环境中,即通过迭代 parent.frame(i) 遇到的所有环境,其中iseq_len(sys.nframe()) 中。此外,我希望应用标准范围规则。

这是一个矛盾:R 中的标准作用域是词法的,但我在这里描述的是动态作用域(感谢@MikkoMarttila 帮助我解决了这个问题)。我真正想要的是一种替代任何变量的方法,该变量不是在一个特定环境中绑定,而是在所有父封闭环境中绑定,可以通过重复应用 base::parent.env().

来枚举其集合

考虑以下示例:

do_something <- function(todo) {
  cat(
    paste(
      deparse(substitute(todo, environment())),
      collapse = "\n"
    )
  )
}

nested_do <- function() {

  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  })

}

var_1 <- "hello"

nested_do()

目前这给

print(var_1)
print("world")
print(var_2)

我想要的地方

print("hello")
print("world")
print("goodbye")

我查看了 base::bquote()rlang::enexpr(),但对于这两者,我必须用 .()!! 显式标记 substitution/unquoting 的变量。我宁愿不必手动指定变量,而是解决所有找到的问题(就像在 base::substitute() 中一样)。此外,我尝试将 base::substitute() 与相应的 env 参数进行迭代应用,并且我查看了 oshka::expand(),但我没有尝试过任何我需要的东西。

非常感谢任何帮助。

其他上下文

我想要实现的目标如下:我在集群 运行 LSF 上工作。这意味着我可以使用提交工具 bsub 提交作业,该工具可以将 R 文件作为输入。现在我想要一个生成这些输入文件的脚本(例如使用函数 do_something())。

long_running_fun <- function(x) {
  Sys.sleep(100)
  x / 2
}

var_1 <- 2 + 2
var_2 <- var_1 + 10

do_something({
  print(var_1)
  var_3 <- long_running_fun(var_2)
  print(var_3)
})

在上述情况下,我希望将以下内容(或等效内容)写入文件

print(4)
var_3 <- long_running_fun(14)
print(var_3)

与其这样做,我建议您像这样传递环境:

esubstitute <- function(expr, envir) do.call("substitute", list(expr, envir))

do_something <- function(todo, envir = parent.frame()) {
  cat(
    paste(
      deparse(esubstitute(todo, envir)),
      collapse = "\n"
    )
  )
}

nested_do <- function(envir = parent.frame()) {

  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  }, envir)

}

var_1 <- "hello"

nested_do()

给予:

[1] "hello"
[1] "world"
[1] "goodbye"
"goodbye"> 

您可能还想查看 envnames 包。

您可以定义一个函数来执行这样的替换序列: 也就是说,取一个表达式并将其替换为所有 调用堆栈中的环境。这是一种方法:

substitute_stack <- function(expr) {
  expr <- substitute(expr)

  # Substitute in all envs in the call stack
  envs <- rev(sys.frames())
  for (e in envs) {
    expr <- substitute_q(expr, e)
  }

  # sys.frames() does not include globalenv() and
  # substitute() doesnt "substitute" there
  e <- as.list(globalenv())
  substitute_q(expr, e)
}

# A helper to substitute() in a pre-quoted expression
substitute_q <- function(expr, env = parent.frame()) {
  eval(substitute(substitute(x, env), list(x = expr)))
}

让我们试一试:

do_something <- function(todo) {
  cat(
    paste(
      deparse(substitute_stack(todo)),
      collapse = "\n"
    )
  )
}

nested_do <- function() {
  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  })
}

var_1 <- "hello"

nested_do()
#> {
#>     print("hello")
#>     print("world")
#>     print("goodbye")
#> }

实际上这样做是否是个好主意是另一个问题。 可能更受欢迎。

reprex package (v0.2.0.9000) 创建于 2018-07-19。

的基础上,我认为以下内容符合我的要求

do_something <- function(todo) {

  # A helper to substitute() in a pre-quoted expression
  substitute_q <- function(expr, env) {
    eval(substitute(substitute(x, env), list(x = expr)))
  }

  substitute_parents <- function(expr) {
    expr <- substitute(expr)

    # list all parent envs
    envs <- list()
    env <- environment()
    while (!identical(env, globalenv())) {
      envs <- c(envs, env)
      env <- parent.env(env)
    }
    # substitute in all parent envs
    for (e in envs) {
      expr <- substitute_q(expr, e)
    }

    # previously did not include globalenv() and
    # substitute() doesnt "substitute" there
    e <- as.list(globalenv())
    substitute_q(expr, e)
  }

  cat(
    paste(
      deparse(substitute_parents(todo)),
      collapse = "\n"
    )
  )
}

这给出了

nested_do <- function() {
  var_2 <- "not_this"

  do_something({
    print(var_1)
    Sys.sleep(100)
    print("world")
    print(var_2)
  })
}

var_1 <- "hello"
var_2 <- "goodbye"

do_something({
  print(var_1)
  Sys.sleep(100)
  print("world")
  print(var_2)
})
#> {
#>     print("hello")
#>     Sys.sleep(100)
#>     print("world")
#>     print("goodbye")
#> }
nested_do()
#> {
#>     print("hello")
#>     Sys.sleep(100)
#>     print("world")
#>     print("goodbye")
#> }