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
上面的方法很好,因为你自己不需要再输**入引号了**。但是这里又出现了一个新的问题,
```r
name <- "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他的参数呢?
# works
library(MASS)
# fails
MASS
#> Error in eval(expr, envir, enclos): object 'MASS' not found
一个参数在函数内可以运行,但是在函数外不能运行。则表明他是quote
3. quoting
3. 1Capturing expressions
一共有四个重要的quoting functions,其中最重要的是expr()
expr(x + y)
#> x + y
expr(1 / 2 / 3)
#> 1/2/3
要记得,expr()不会捕获括号(以及空白符“ ”
想要在函数中捕获express,就需要用enexpr()
f1 <- function(x) expr(x)
f1(a + b + c)
#> x
f2 <- 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]]
#> x
f("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) * 2
f4 <- 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 occurred
f4 <- 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 default
expr(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 names
ys <- 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] FALSE
x <- quote(variable)
!!x
#> Error in !x: invalid argument type
df <- data.frame(x = 1:5)
y <- 100
with(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 4
tibble::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: