在以下示例中,为什么我们比f1更喜欢使用f2?从某种意义上说它更有效率吗?对于以前以R为基础的用户,使用“替换+评估”选项似乎更为自然。

library(dplyr)

d = data.frame(x = 1:5,
               y = rnorm(5))

# using enquo + !!
f1 = function(mydata, myvar) {
  m = enquo(myvar)
  mydata %>%
    mutate(two_y = 2 * !!m)
}

# using substitute + eval
f2 = function(mydata, myvar) {
  m = substitute(myvar)
  mydata %>%
    mutate(two_y = 2 * eval(m))
}

all.equal(d %>% f1(y), d %>% f2(y)) # TRUE

换句话说,除了这个特定的示例之外,我的问题是:我可以摆脱使用具有良好基础R的dplyr NSE函数进行编程的麻烦,例如replace + eval,还是真的需要学习爱上所有这些rlang函数,因为它有好处吗(速度,清晰度,组成性...)?

最佳答案

我想给出一个独立于dplyr的答案,因为与enquo相比,使用substitute有一个非常明显的优势。两者都在函数的调用环境中进行查找,以识别提供给该函数的表达式。区别在于substitute()仅执行一次,而!!enquo()将正确遍历整个调用堆栈。

考虑一个使用substitute()的简单函数:

f <- function( myExpr ) {
  eval( substitute(myExpr), list(a=2, b=3) )
}

f(a+b)   # 5
f(a*b)   # 6

当调用嵌套在另一个函数中时,此功能将中断:
g <- function( myExpr ) {
  val <- f( substitute(myExpr) )
  ## Do some stuff
  val
}

g(a+b)
# myExpr     <-- OOPS

现在考虑使用enquo()重写的相同函数:
library( rlang )

f2 <- function( myExpr ) {
  eval_tidy( enquo(myExpr), list(a=2, b=3) )
}

g2 <- function( myExpr ) {
  val <- f2( !!enquo(myExpr) )
  val
}

g2( a+b )    # 5
g2( b/a )    # 1.5

这就是为什么enquo() + !!优于substitute() + eval()的原因。 dplyr只是简单地充分利用此属性来构建一组连贯的NSE函数。

更新: rlang 0.4.0引入了一个新的运算符{{(发音为“curly curl”),实际上是!!enquo()的简写形式。这使我们可以将g2的定义简化为
g2 <- function( myExpr ) {
  val <- f2( {{myExpr}} )
  val
}

关于r - 为什么是enquo + !!最好替代+评估,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49700912/

10-13 00:00