【问题标题】:substitute LHS in `=` operator with rlang tidyeval inside Sys.setenv用 Sys.setenv 中的 rlang tidyeval 替换 `=` 运算符中的 LHS
【发布时间】:2023-05-26 09:21:01
【问题描述】:

问题描述

Sys.setenv 没有简单的接口来提供 LHS(环境变量名称)作为参数。如果想要动态定义应该设置什么环境变量,则需要元编程方法。

基础 R 方式

这个小辅助函数按预期工作。

setenv = function(var, value, quiet=TRUE) {
  stopifnot(is.character(var), !is.na(var), length(value)==1L, is.atomic(value))
  qc = as.call(c(list(quote(Sys.setenv)), setNames(list(value), var)))
  if (!quiet) print(qc)
  eval(qc)
}

var_name = "RISCOOL"
Sys.getenv(var_name)
#[1] ""
setenv(var_name, value=150, quiet=FALSE)
#Sys.setenv(RISCOOL = 150)
Sys.getenv(var_name)
#[1] "150"

问题

问题是如何使用 pryrrlang (tidyeval) 之类的软件包解决问题?或者最终是另一个受欢迎的。
我根本不知道这些包,希望更好地了解它们如何简化我的元编程代码。

请注意,问题是关于元编程的,设置 env var 只是一个示例。

【问题讨论】:

    标签: r metaprogramming rlang tidyeval pryr


    【解决方案1】:

    如果要使用rlang-style quasiquotation构造调用并直接求值,需要blast()

    blast <- function(expr, env = caller_env()) {
      eval_bare(enexpr(expr), env)
    }
    
    vars <- c(A = "a", B = "b", C = "c")
    
    blast(data.frame(!!!vars))
    #>   A B C
    #> 1 a b c
    

    在您的原始示例中,您需要取消引用名称。我们尚不支持对 := 的 LHS 进行深度取消引用(请参阅 https://github.com/r-lib/rlang/issues/279),但您可以改用 !!!

    setenv <- function(var, value) {
      args <- setNames(value, var)
      blast(Sys.setenv(!!!args))
    }
    
    setenv("foobar", 1)
    #> [1] TRUE
    
    Sys.getenv("foobar")
    #> [1] "1"
    

    要插入打印调用,blast 级别太高,但可以使用组件:

    setenv <- function(var, value, quiet = FALSE) {
      args <- setNames(value, var)
      call <- expr(Sys.setenv(!!!args))
    
      if (!quiet) {
        print(call)
      }
    
      # Evaluate in our own environment where `Sys.setenv()` is defined
      # (and protected if we're in a package namespace)
      eval(call)
    }
    

    【讨论】:

    • 似乎是我正在寻找的,但您的示例可能与问题中的示例相匹配。
    • 我已添加setenv() 示例。
    【解决方案2】:

    使用do.call:

    var_name = "RISCOOL"
    do.call("Sys.setenv", as.list(setNames(3, var_name)))
    
    # check that it worked
    Sys.getenv(var_name)
    ## [1] "3"
    

    或使用咕噜声

    library(purrr)
    invoke("Sys.setenv", set_names(4, var_name))
    

    【讨论】:

    • 这可能是更好的基本 R 方式,但问题是关于 rlangpryr 我想更好地理解。实际上,我更喜欢在评估之前选择打印呼叫,而这对于 do.call 是不可行的。
    • 创建一个调用然后在运行时评估它不是很好的编程习惯。这意味着您必须在运行时拥有所有可用的 R 才能进行评估。如果您必须使用 tidyverse 执行此操作,请使用 purrr 中的调用和 rlang 中的 set_names。我已经添加了这个来回答。
    • “你必须在运行时拥有所有可用的 R 才能进行评估”——我不明白这一点。它不必是 tidyverse 方式,而是一些流行的元编程包,看看它如何帮助完成这样的任务。 purrr 示例是另一种很好的方法。谢谢
    • 如果问题需要eval 那么当然使用它,但如果你可以避免它,你应该这样做。
    • eval 无论如何都会被do.call 调用,只是没有明确地使用eval 函数,不是吗?
    【解决方案3】:

    我认为您需要使用:=。 它的用法在dplyr vignettes 之一中进行了解释, 但该功能由rlang 提供。 在这种情况下你可以使用call2:

    setenv <- function(var, val) {
      rlang::call2("Sys.setenv", !!rlang::enexpr(var) := val)
    }
    
    setenv(foo, "bar")
    # Sys.setenv(foo = "bar")
    

    只需根据需要添加eval 呼叫。

    【讨论】:

    • AFAIU 如果不将 call2 调用包装在额外的函数中,这将无法工作?
    • @jangorecki 如果没有额外的eval,它将无法评估,我将其省略只是为了显示构造的调用。
    【解决方案4】:

    只需使用do.call

    lst <- structure(list(value), names=name)
    do.call(Sys.setenv, lst)
    

    【讨论】:

    • 这可能是更好的基本 R 方式,但问题是关于 rlangpryr 我想更好地理解。实际上,我更喜欢在评估之前选择打印呼叫,这对于 do.call 是不可行的。
    最近更新 更多