为什么可以在 dplyr 辅助函数 "across" 中检测到 tidyselect 辅助函数 "where"?

Why the tidyselect helper function "where" can be detected inside the dplyr helper function "across"?

“tidyselect”包提供了一个 select 辅助函数 wherewhere 用于 select 带有自定义函数的数据框列。它是“tidyselect”的一个内部函数。这意味着 where 不会加载到您的命名空间,您只能通过 tidyselect:::where.

调用它

不过,我从dplyr vignettes: columnwise operations.

看到了下面的例子
starwars %>% 
  summarise(across(where(is.character), ~ length(unique(.x))))
#> # A tibble: 1 x 8
#>    name hair_color skin_color eye_color   sex gender homeworld species
#>   <int>      <int>      <int>     <int> <int>  <int>     <int>   <int>
#> 1    87         13         31        15     5      3        49      38

在这个例子中,where 没有前缀“tidyselect:::”,但很明显,代码中没有错误,并且产生了有意义的结果。这对我来说似乎很奇怪。我想知道为什么代码运行正常

我猜这是由于“代码引用”,它是 tidyeval 方法的一部分。粗略地说,代码引用将代码暂停为表达式,然后在“内部环境”中对表达式求值。这只是一个直观的猜测,我不知道如何测试它。

我希望有人能帮我解决“哪里”的问题,或者留下一些关于代码如何为我工作的参考。

您没有说明示例中附加了哪些包,但我们假设唯一附加的包是 dplyr

library(dplyr)

首先,我们注意到函数 where 未附加,即当前 R 会话不知道。我们可以通过在控制台中键入其名称(不带括号)来进行检查。如果附加了该函数,我们现在将看到它的源代码。相反,我们收到一个错误,指出未找到对象 where

但是,我们注意到 dplyr 附加了 tidyselect 的其他功能,以 starts_with 为例。如果我们重复在控制台中键入名称的实验,我们现在可以看到源代码以及函数源自 tidyselect 命名空间:

> starts_with
function (match, ignore.case = TRUE, vars = NULL) 
{
    check_match(match)
    vars <- vars %||% peek_vars(fn = "starts_with")
    if (ignore.case) {
        vars <- tolower(vars)
        match <- tolower(match)
    }
    flat_map_int(match, starts_with_impl, vars)
}
<bytecode: 0x0000027338e5f8e8>
<environment: namespace:tidyselect>

在这种情况下,函数 starts_with 由 dplyr 使用 NAMESPACE 文件附加,您可以在其中列出其他包中应与您的包一起附加的函数。您可以查看 dplyr source code.

但是 where 并没有像我们已经看到的那样以这种方式提供。在这种情况下,该函数确实被引用并且仅在 tidyselect 包中进行评估。如果您查看 source code for across,您会注意到在第 82 行中,列规范传递给同一文件中定义的函数 across_setup。在此函数中,列规范被引用(第 174、175 行),然后发送到 tidyselect 函数 tidyselect::eval_select(第 177 行)。这个函数是 tidyselect 包的一部分,可以访问 where.