【问题标题】:Accept both bare (from rlang) or string as a function input接受裸(来自 rlang)或字符串作为函数输入
【发布时间】:2019-12-14 00:15:13
【问题描述】:

我正在编辑包中的现有函数。目前,该函数接受数据框中的列名作为字符串。我正在更新函数以接受字符串名称或裸名称。但我遇到了一些问题。

我想采用的一般方法是将裸字符转换为字符串,因此不需要更新函数的其余部分。如果用户传递的是字符串列名,那么我不需要修改输入。

下面的代码将裸输入转换为字符串,但我不知道如何有条件地转换为字符串或不修改字符串。

test_fun <- function(by) {
  # convert to enquo
  by1 <- rlang::enquo(by)

  # convert enquo to string
  by2 <- rlang::quo_text(by1)

  by2
}

# converts to string
test_fun(varname)

# not sure how to pass this unmodified
test_fun("varname")

【问题讨论】:

  • 我强烈建议不要这样做,因为它不可避免地会导致歧义。让您的 API 有主见且明确,即接受名称或字符串,而不是两者。
  • 感谢@KonradRudolph 的输入!我喜欢这个建议考虑到这一点,有没有办法要求裸输入,但打印一个字符串输入已被弃用的注释?这是我公司内许多人使用的包,如果我对 API 进行更改,我希望更改尽可能清晰。
  • 实际上,弃用警告是不错的,而且我过去自己也做过。

标签: r rlang quosure


【解决方案1】:

rlang::ensym() 几乎就是为了这个目的而存在的,只是它的输出是一个名称而不是一个字符串,所以你需要转换它。

test_fun <- function(by) {
  as.character(rlang::ensym(by))
}

test_fun(varname)
#> [1] "varname"

test_fun("varname")
#> [1] "varname"

reprex package (v0.2.1) 于 2019 年 8 月 8 日创建

我认为这样做不一定是坏事,foo &lt;- "bar""foo" &lt;- "bar" 是等价的,"head"(iris)head(iris) 是等价的,ensym() 可以很容易地拥有像 select(iris, "Species") 和 @ 这样的东西987654332@ 等价。它便于交互使用,如果您希望您的功能与dplyr::select() 甚至base::library() 等保持一致,那么不支持此功能确实会更令人惊讶。

只要确保它在您的用例中有意义,否则它可能确实会令人困惑。

如果你想要一个弃用警告,你可以使用:

test_fun <- function(by) {
  if(is.character(rlang::enexpr(by)))
    warning("literal string input is deprecated, please use raw variable names")
  as.character(rlang::ensym(by))
}

test_fun(varname)
#> [1] "varname"

test_fun("varname")
#> Warning in test_fun("varname"): literal string input is deprecated, please use raw
#> variable names
#> [1] "varname"

reprex package (v0.2.1) 于 2019 年 8 月 8 日创建

【讨论】:

  • 这将是我的首选解决方案,因为rlang::ensym() 标准化了输入。唯一要补充的是,根据 OP 想要在下游做什么,可能不需要将符号转换为字符串(例如,dplyr 函数可以同时理解字符串和符号)。使用多个参数时,有时it's even cleaner to forward them directly to dplyr 无需自己进行任何修改。
【解决方案2】:

如前所述,我强烈建议不要接受多种类型,如果这会造成歧义(这里确实如此)。

也就是说,下面是这样做的:

test_fun = function (by) {
    by = substitute(by)
    if (is.name(by)) {
        as.character(by)
    } else if (is.character(by)) {
        by
    } else {
        stop('Unexpected type')
    }
}

在这种情况下,使用 rlang 不会简化代码。

【讨论】:

    【解决方案3】:

    我同意@Konrad 的评论,但您可以使用 base R 轻松做到这一点:

    test_fun <- function(by) {
    
      res <- substitute(by)
    
      if (is.character(res)) return(res)
      if (is.name(res)) return(deparse(res))
    
      stop("unsupported input")
    }
    
    test_fun(varname)
    #[1] "varname"
    
    test_fun("varname")
    #[1] "varname"
    
    test_fun(y ~ x)
    #Error in test_fun(y ~ x) : unsupported input
    

    【讨论】:

      猜你喜欢
      • 2017-05-05
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多