1. Quasiquatation
quotation is the act of capturing an unevaluated expression
unquotation is the ability to selectively evaluate parts of an otherwise quoted expression.
three pillars of tidy evaluation:
- Quasiquotation
- quosures
- data mask
2. Motivation
首先,你创建了如下的函数 ```r cement <- function(…) { args <- ensyms(…) paste(purrr::map(args, as_string), collapse = “ “) #as_string,把expression变成string }
cement(Good, morning, Hadley)
> [1] “Good morning Hadley”
cement(Good, afternoon, Alice)
> [1] “Good afternoon Alice”
已知的是:this function **quotes all of its inputs,**你可以认为这是为每一个参数都打上了一个引号。但是这不是字符串,因为它产生的中间对象是expression,而不是string上面的方法很好,因为你自己不需要再输**入引号了**。但是这里又出现了一个新的问题,```rname <- "Hadley"time <- "morning"cement(Good, time, name)#> [1] "Good time name"
由于cement会自动quote输入的参数,所以该方程行不通
但是我们可以使用!!,主动unquote掉这个参数
cement(Good, !!time, !!name)#> [1] "Good morning Hadley"
2.1 Vocabulary
quoted VS evaluated arguments
- An evaluated argument obeys R’s usual evaluation rules.
- A quoted argument is captured by the function, and is processed in some custom way.
paste() evaluates all its arguments; cement() quotes all its arguments.
如何判断一个函数是evaluate还是quote他的参数呢?
# workslibrary(MASS)# failsMASS#> Error in eval(expr, envir, enclos): object 'MASS' not found
一个参数在函数内可以运行,但是在函数外不能运行。则表明他是quote
3. quoting
3. 1Capturing expressions
一共有四个重要的quoting functions,其中最重要的是expr()
expr(x + y)#> x + yexpr(1 / 2 / 3)#> 1/2/3
要记得,expr()不会捕获括号(以及空白符“ ”
想要在函数中捕获express,就需要用enexpr()
f1 <- function(x) expr(x)f1(a + b + c)#> xf2 <- function(x) enexpr(x)f2(a + b + c)#> a + b + c
To capture all arguments in …, use enexprs().
f <- function(...) enexprs(...)f(x = 1, y = 10 * z)#> $x#> [1] 1#>#> $y#> 10 * z
最后,exprs()还可以用于make a list of expressions:
exprs(x = x ^ 2, y = y ^ 3, z = z ^ 4)# shorthand for# list(x = expr(x ^ 2), y = expr(y ^ 3), z = expr(z ^ 4))
3. 2 Capturing symbols
如果你想让用户使用变量名,而不是随意的expression,这时候你可以使用ensym() or ensyms()
f <- function(...) ensyms(...)f(x)#> [[1]]#> xf("x")#> [[1]]#> x
3.3 with base R
The base equivalent of expr() is quote():
quote(x + y)#> x + y
The base function closest to enexpr() is substitute():
f3 <- function(x) substitute(x)f3(x + y)#> x + y
The base equivalent to exprs() is alist():
alist(x = 1, y = x + 2)#> $x#> [1] 1#>#> $y#> x + 2
The equivalent to enexprs() is an undocumented feature of substitute()97:
f <- function(...) as.list(substitute(...()))f(x = 1, y = 10 * z)#> $x#> [1] 1#>#> $y#> 10 * z
3.4 Substitution
f4 <- function(x) substitute(x * 2)f4(a + b + c)# (a + b + c) * 2f4 <- function(x) enexpr(x * 2)f4(a + b + c)# Error in `enexpr()`:# ! `arg` must be a symbol# Run `rlang::last_error()` to see where the error occurredf4 <- function(x) enexpr(x)f4(a + b + c)# a + b + c
substitution除了做quoting,它还可以做替换,如果你给的是一个表达式而不是一个symbol,他会自动替换symbol代表的东西
substitute(x * y * z, list(x = 10, y = quote(a + b)))#> 10 * (a + b) * z
4. Unquoting
目前为止,我们还未了解到rlang中quoting functions和base R中的quoting functions的区别。最大的不同就是说,rlang的quoting functions实际上是在quasiquoting functions,因为它同样可以unquote,而base R不能unquote。
Unquoting is one inverse of quoting. It allows you to selectively evaluate code inside expr(),因此expr(!!x) is equivalent to x
4.1 Unquoting one argument
Use !! to unquote a single argument in a function call. !! takes a single expression, evaluates it, and inlines the result in the AST.
x <- expr(-1)expr(f(!!x, y))#> f(-1, y)

!!本质上就是把你的参数在进行一个转化,类似于扒皮。
除了作用于对象,object。!!也可以作用于symbol和常数
mean_rm <- function(var) {var <- ensym(var)expr(mean(!!var, na.rm = TRUE))}expr(!!mean_rm(x) + !!mean_rm(y))#> mean(x, na.rm = TRUE) + mean(y, na.rm = TRUE)
If the right-hand side of !! is a function call, !! will evaluate it and insert the results:(如果!!的右边是一个function call),那么!!会执行它,并且把它插入到结果里
mean_rm <- function(var) {var <- ensym(var)expr(mean(!!var, na.rm = TRUE))}expr(!!mean_rm(x) + !!mean_rm(y))#> mean(x, na.rm = TRUE) + mean(y, na.rm = TRUE)
!! preserves operator precedence because it works with expressions.
x1 <- expr(x + 1)x2 <- expr(x + 2)expr(!!x1 / !!x2)#> (x + 1)/(x + 2)
4.2 Unquoting a function
expr(!!f(x, y)) unquotes the result of f(x, y), so you need an extra pair of parentheses.
f <- expr(foo)expr((!!f)(x, y))#> foo(x, y)f <- expr(pkg::foo)expr((!!f)(x, y))#> pkg::foo(x, y)
4.3 Unquoting a missing argument
有时候,引用一个missing argument 会很有用
arg <- missing_arg()expr(foo(!!arg, !!arg))#> Error in enexpr(expr): argument "arg" is missing, with no defaultexpr(foo(!!maybe_missing(arg), !!maybe_missing(arg)))#> foo(, )
4.4 Unquoting in special forms
expr(df$!!x)#> Error: unexpected '!' in "expr(df$!"#To make unquoting work, you’ll need to use the prefix form (Section 6.8.1):x <- expr(x)expr(`$`(df, !!x))#> df$x
4.5 Unquoting many arguments
use !!!
xs <- exprs(1, a, -b)expr(f(!!!xs, y))#> f(1, a, -b, y)# Or with namesys <- set_names(xs, c("a", "b", "c"))expr(f(!!!ys, d = 4))#> f(a = 1, b = a, c = -b, d = 4)
4.6 The polite fiction of !!
!!TRUE#> [1] TRUE!!!TRUE#> [1] FALSEx <- quote(variable)!!x#> Error in !x: invalid argument typedf <- data.frame(x = 1:5)y <- 100with(df, x + !!y) # !!y=TRUE#> [1] 2 3 4 5 6
4.7 Non-standard ASTs
x1 <- expr(class(!!data.frame(x = 10)))x1#> class(list(x = 10))eval(x1)#> [1] "data.frame"
这里你是不是觉得应该是“list1”?
5. Non-quoting
Base R has one function that implements quasiquotation: bquote(). It uses .() for unquoting:
6. … (dot-dot-dot)
dfs <- list(a = data.frame(x = 1, y = 2),b = data.frame(x = 3, y = 4))var <- "x"val <- c(4, 3, 9)# 如何合并一个list中的数据框dplyr::bind_rows(!!!dfs)#> x y#> 1 1 2#> 2 3 4tibble::tibble(!!var := val)#> # A tibble: 3 x 1#> x#> <dbl>#> 1 4#> 2 3#> 3 9
Row-binding multiple data frames is like unquote-splicing: we want to inline individual elements of the list into the call:
