如何制作支持引用和非引用参数的tidyverse函数?

10

我知道如何制作支持使用 dplyr::enquo(variable) 对无引号函数参数 'variable' 进行准引用(quasi-quotation)的函数,或者需要对参数进行引用(quote)的函数,可以使用 rlang::sym("variable")。有没有一种简单的方法可以使函数支持引用和无引用参数呢?

例如,dplyr::select() 同时允许 select(mtcars, mpg)select(mtcars, "mpg") 两种方式。在构建一个能够同时支持两种方式的函数时,有一个需要考虑的问题是数据屏蔽(data masking)的影响,但我不确定是否需要在构建更复杂的函数时加以考虑。

我一直在查看 dplyr 函数的 GitHub 页面,但像 select 这样的简单函数都依赖于一个全新的包(tidyselect),所以情况比较复杂。我也没有在 Tidy evaluation book 中看到清晰的解释。下面是一个支持引用和无引用参数的 hack 函数,但这不是一个可靠的解决方案。我相信有更简单的方法。

library(dplyr)

data(mtcars)

test_func <- function(variable) {
  if(nrow(count(mtcars, {{variable}})) == 1) {
    variable <- rlang::sym(variable)
  }
  count(mtcars, {{variable}})
}

all_equal(
  test_func(cyl),
  test_func("cyl")
)

2
这种UI没有实际用途,因为你无法将字符串保存在变量中并以正常方式引用它。如果你真的想这样做,可以像akrun建议的那样使用ensym(),它实现了与=左侧或library()函数中相同的语义。 - Lionel Henry
我承认支持两种类型并没有太大的收益,我对这种方法的缺点很感兴趣。在哪些情况下ensym()会失败?如果您需要将其存储为变量(例如在ggplot中进行标记),您不能将符号转换回字符字符串吗? - bholly
2
缺点是,如果有人看到 foo("bar"),他们可能会尝试 quux <- "bar"; foo(quux),并且会困惑为什么它不能以同样的方式工作。 - Lionel Henry
1个回答

10
如果需要在带引号和不带引号的情况下使用,请使用ensym
test_func <- function(variable) {  

    dplyr::count(mtcars, !!rlang::ensym(variable))    

  }

-测试

test_func(cyl)
#  cyl  n
#1   4 11
#2   6  7
#3   8 14
test_func('cyl')
#  cyl  n
#1   4 11
#2   6  7
#3   8 14

注意:最好将数据也作为函数的参数之一


谢谢!与要求参数引用或不引用相比,这种方法有主要的缺点吗?我问这个问题是因为在整洁评估源中,ensym()的讨论要少得多,而ensym()乍一看更加灵活多样。 - bholly
1
@bholly 我认为大多数人选择使用enquo,是因为他们想将未引用的字符串作为参数传递,而不是引用的。而对于引用的字符串,有很多选项。例如,在“基本R”中,你可以使用“[[”或“[”来对列进行子集操作,但未引用的字符串需要使用substitute/deparse函数。 - akrun
1
@bholly,如果这是在生产环境中,最好有一个Confluence页面,在其中指定传递的参数及其期望值,以便用户不会传递带引号和不带引号的参数。 - akrun
我计划在函数文档中包含对参数的期望说明(我正在处理的函数位于私有存储库中的软件包内)。类似于dplyr::filter()...参数的整洁评估注释。 - bholly
@akrun 对于同一个例子,如果我这样做为什么不起作用呢?value <- "cyl"test_func(value) - Arun Kumaar
@ArunKumaar 你需要转义 test_func(!!value) 来评估 value 中的内容,否则它会认为你正在传递字面量 value - akrun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接