rlang::sym 在匿名函数中

rlang::sym in anonymous functions

我最近注意到 rlang::sym 在匿名函数中似乎不起作用,我不明白为什么。这是一个例子,它很笨拙和丑陋,但我认为它说明了这一点

require(tidyverse)
data <- tibble(x1 = letters[1:3],
               x2 = letters[4:6],
               val = 1:3)

get_it <- function(a, b){
    data %>%
        mutate(y1 = !!rlang::sym(a)) %>%
        mutate(y2 = !!rlang::sym(b)) %>%
        select(y1, y2, val)
}
get_it("x1", "x2")

这定义了一些玩具数据和一个(可怕的)函数,该函数本质上是根据列名重命名列。现在我可以对 a 和 b 的不同组合做同样的事情:

d <- tibble(x = c("x1", "x2"),
            y = c("x2", "x1"))
d %>% mutate(tmp = map2(x, y, get_it))

但是,如果我尝试使用匿名函数执行完全相同的操作,则它不起作用:

d %>% mutate(tmp = map2(x, y, function(a, b){
data %>%
    mutate(y1 = !!rlang::sym(a)) %>%
    mutate(y2 = !!rlang::sym(b)) %>%
    select(y1, y2, val)
}))

这失败了 object 'a' not found 即使功能完全相同只是在这里它是匿名的。谁能解释一下为什么?

问题不是匿名函数,而是 !! 的运算符优先级。 !! 的帮助页面指出

The !! operator unquotes its argument. It gets evaluated immediately in the surrounding context.

这意味着当您编写一个复杂的 NSE 表达式时,例如 mutate 中的 select,取消引用将在整个表达式的环境中发生。正如@lionel 所指出的,取消引用优先于其他事情,例如匿名函数环境的创建。

在您的情况下,!! 取消引号是针对外部 mutate() 完成的,然后它会尝试在 d 中找到列 x1,而不是 [=20] =].有两种可能的解决方案:

1) 将涉及 !! 的表达式拉入独立函数(正如您在问题中所做的那样):

res1 <- d %>% mutate(tmp = map2(x, y, get_it))

2) 将 !! 替换为 eval 以延迟表达式计算:

res2 <- d %>% mutate(tmp = map2(x, y, function(a, b){
  data %>%
    mutate(y1 = eval(rlang::sym(a))) %>%
    mutate(y2 = eval(rlang::sym(b))) %>%
    select(y1, y2, val)
}))

identical(res1, res2)       #TRUE