简体   繁体   English

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

[英]R: Substitute variables bound in all parent environments

The function base::substitute(expr, env) , as per its documentation page, 函数base::substitute(expr, env) ,根据其文档页面,

returns the parse tree for the (unevaluated) expression expr, substituting any variables bound in env. 返回(未求值的)表达式expr的分析树,用env中绑定的任何变量替换。

I am looking for a way of substituting any variables bound not in one specific environment, but in all environments in the current call stack, ie all environments encountered by iterating over parent.frame(i) where i is in seq_len(sys.nframe()) . 我在寻找替代在一个特定的环境没有绑定任何变量的方法,但是在当前调用堆栈所有的环境,即通过遍历遇到的所有环境parent.frame(i)其中iseq_len(sys.nframe()) Additionally, I'd like standard scoping rules to apply. 另外,我想应用标准范围规则。

This is a contradiction: standard scoping in R is lexical, but what I describe here is dynamic scoping (thank you @MikkoMarttila for helping me clear this up). 这是一个矛盾:R中的标准作用域是词汇,但是我在这里描述的是动态作用域(感谢@MikkoMarttila帮助我解决了这一问题)。 What I actually want is a way of substituting any variables bound not in one specific environment, but in all parent enclosing environments, the set of which can be enumerated by repeatedly applying base::parent.env() . 我真正想要的是一种替代任何不在一个特定环境中绑定但在所有父封闭环境中绑定的变量的方法,可以通过重复应用base::parent.env()来枚举其集合。

Consider the following example: 考虑以下示例:

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

Currently this gives 目前,这给

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

where I'd like to have 我想要的地方

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

I have looked at base::bquote() and rlang::enexpr() but for both I have to explicitly mark the variables for substitution/unquoting with .() or !! 我已经看过base::bquote()rlang::enexpr()但对于两者,我都必须用.()!!明确标记要替换/取消引用的变量.() . I'd rather not have to specify variables manually, but have everything resolved that is found (just like in base::substitute() ). 我宁愿不必手动指定变量,而是要解决找到的所有问题(就像在base::substitute() )。 Furthermore, I tried iteratively applying base::substitute() with the respective env arguments and I had a look at oshka::expand() , but nothing I tried, does what I need. 此外,我尝试使用相应的env参数迭代地应用base::substitute() ,然后查看了oshka::expand() ,但是我没有尝试过,但是却oshka::expand()我的需要。

Any help is much appreciated. 任何帮助深表感谢。

Additional context 其他背景

What I'm trying to achieve is the following: I'm working on a cluster running LSF. 我要实现的目标如下:我正在运行LSF的集群上工作。 This means that I can submit jobs using the submission tool bsub which may take an R file as input. 这意味着我可以使用提交工具bsub提交作业,该工具可以将R文件作为输入。 Now I would like to have a script that generates these input files (eg using the function do_something() ). 现在,我想拥有一个生成这些输入文件的脚本(例如,使用函数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)
})

I in the above case, want the following (or something equivalent) to be written to a file 在上述情况下,我希望将以下内容(或等效内容)写入文件

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

Rather than do that I suggest you just pass the environment like this: 与其说我建议您通过这样的环境,不如说是:

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

giving: 赠送:

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

You may also want to look at the envnames package. 您可能还需要查看envnames软件包。

You can define a function to do such a substitution sequence: that is, take an expression and substitute it in all of the environments in the call stack. 您可以定义一个函数来执行这样的替换序列:即,将一个表达式替换为调用堆栈中的所有环境。 Here's one way: 这是一种方法:

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

Let's give it a go: 让我们开始吧:

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

Whether or not actually doing this is a good idea is a whole other question. 实际执行此操作是否是一个好主意是另一个问题。 The approach suggested by @G.Grothendieck is likely to be preferrable. @ G.Grothendieck建议方法可能更可取。

Created on 2018-07-19 by the reprex package (v0.2.0.9000). reprex软件包 (v0.2.0.9000)创建于2018-07-19。

Building on @MikkoMarttila's answer , I think the following does what I requested @MikkoMarttila的答案为基础 ,我认为以下内容可以满足我的要求

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

This gives 这给

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM