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:

  1. Quasiquotation
  2. quosures
  3. 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”

  1. 已知的是:this function **quotes all of its inputs,**你可以认为这是为每一个参数都打上了一个引号。但是这不是字符串,因为它产生的中间对象是expression,而不是string
  2. 上面的方法很好,因为你自己不需要再输**入引号了**。但是这里又出现了一个新的问题,
  3. ```r
  4. name <- "Hadley"
  5. time <- "morning"
  6. cement(Good, time, name)
  7. #> [1] "Good time name"

由于cement会自动quote输入的参数,所以该方程行不通
但是我们可以使用!!,主动unquote掉这个参数

  1. cement(Good, !!time, !!name)
  2. #> [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他的参数呢?

  1. # works
  2. library(MASS)
  3. # fails
  4. MASS
  5. #> Error in eval(expr, envir, enclos): object 'MASS' not found

一个参数在函数内可以运行,但是在函数外不能运行。则表明他是quote

3. quoting

3. 1Capturing expressions

一共有四个重要的quoting functions,其中最重要的是expr()

  1. expr(x + y)
  2. #> x + y
  3. expr(1 / 2 / 3)
  4. #> 1/2/3

要记得,expr()不会捕获括号(以及空白符“ ”
想要在函数中捕获express,就需要用enexpr()

  1. f1 <- function(x) expr(x)
  2. f1(a + b + c)
  3. #> x
  4. f2 <- function(x) enexpr(x)
  5. f2(a + b + c)
  6. #> a + b + c

To capture all arguments in …, use enexprs().

  1. f <- function(...) enexprs(...)
  2. f(x = 1, y = 10 * z)
  3. #> $x
  4. #> [1] 1
  5. #>
  6. #> $y
  7. #> 10 * z

最后,exprs()还可以用于make a list of expressions:

  1. exprs(x = x ^ 2, y = y ^ 3, z = z ^ 4)
  2. # shorthand for
  3. # list(x = expr(x ^ 2), y = expr(y ^ 3), z = expr(z ^ 4))

3. 2 Capturing symbols

如果你想让用户使用变量名,而不是随意的expression,这时候你可以使用ensym() or ensyms()

  1. f <- function(...) ensyms(...)
  2. f(x)
  3. #> [[1]]
  4. #> x
  5. f("x")
  6. #> [[1]]
  7. #> x

3.3 with base R

The base equivalent of expr() is quote():

  1. quote(x + y)
  2. #> x + y

The base function closest to enexpr() is substitute():

  1. f3 <- function(x) substitute(x)
  2. f3(x + y)
  3. #> x + y

The base equivalent to exprs() is alist():

  1. alist(x = 1, y = x + 2)
  2. #> $x
  3. #> [1] 1
  4. #>
  5. #> $y
  6. #> x + 2

The equivalent to enexprs() is an undocumented feature of substitute()97:

  1. f <- function(...) as.list(substitute(...()))
  2. f(x = 1, y = 10 * z)
  3. #> $x
  4. #> [1] 1
  5. #>
  6. #> $y
  7. #> 10 * z

3.4 Substitution

  1. f4 <- function(x) substitute(x * 2)
  2. f4(a + b + c)
  3. # (a + b + c) * 2
  4. f4 <- function(x) enexpr(x * 2)
  5. f4(a + b + c)
  6. # Error in `enexpr()`:
  7. # ! `arg` must be a symbol
  8. # Run `rlang::last_error()` to see where the error occurred
  9. f4 <- function(x) enexpr(x)
  10. f4(a + b + c)
  11. # a + b + c

substitution除了做quoting,它还可以做替换,如果你给的是一个表达式而不是一个symbol,他会自动替换symbol代表的东西

  1. substitute(x * y * z, list(x = 10, y = quote(a + b)))
  2. #> 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.

  1. x <- expr(-1)
  2. expr(f(!!x, y))
  3. #> f(-1, y)

image.png
!!本质上就是把你的参数在进行一个转化,类似于扒皮。

除了作用于对象,object。!!也可以作用于symbol和常数

  1. mean_rm <- function(var) {
  2. var <- ensym(var)
  3. expr(mean(!!var, na.rm = TRUE))
  4. }
  5. expr(!!mean_rm(x) + !!mean_rm(y))
  6. #> 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),那么!!会执行它,并且把它插入到结果里

  1. mean_rm <- function(var) {
  2. var <- ensym(var)
  3. expr(mean(!!var, na.rm = TRUE))
  4. }
  5. expr(!!mean_rm(x) + !!mean_rm(y))
  6. #> mean(x, na.rm = TRUE) + mean(y, na.rm = TRUE)

!! preserves operator precedence because it works with expressions.

  1. x1 <- expr(x + 1)
  2. x2 <- expr(x + 2)
  3. expr(!!x1 / !!x2)
  4. #> (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.

  1. f <- expr(foo)
  2. expr((!!f)(x, y))
  3. #> foo(x, y)
  4. f <- expr(pkg::foo)
  5. expr((!!f)(x, y))
  6. #> pkg::foo(x, y)

image.png

4.3 Unquoting a missing argument

有时候,引用一个missing argument 会很有用

  1. arg <- missing_arg()
  2. expr(foo(!!arg, !!arg))
  3. #> Error in enexpr(expr): argument "arg" is missing, with no default
  4. expr(foo(!!maybe_missing(arg), !!maybe_missing(arg)))
  5. #> foo(, )

4.4 Unquoting in special forms

  1. expr(df$!!x)
  2. #> Error: unexpected '!' in "expr(df$!"
  3. #To make unquoting work, you’ll need to use the prefix form (Section 6.8.1):
  4. x <- expr(x)
  5. expr(`$`(df, !!x))
  6. #> df$x

4.5 Unquoting many arguments

use !!!

  1. xs <- exprs(1, a, -b)
  2. expr(f(!!!xs, y))
  3. #> f(1, a, -b, y)
  4. # Or with names
  5. ys <- set_names(xs, c("a", "b", "c"))
  6. expr(f(!!!ys, d = 4))
  7. #> f(a = 1, b = a, c = -b, d = 4)

image.png

4.6 The polite fiction of !!

  1. !!TRUE
  2. #> [1] TRUE
  3. !!!TRUE
  4. #> [1] FALSE
  5. x <- quote(variable)
  6. !!x
  7. #> Error in !x: invalid argument type
  8. df <- data.frame(x = 1:5)
  9. y <- 100
  10. with(df, x + !!y) # !!y=TRUE
  11. #> [1] 2 3 4 5 6

4.7 Non-standard ASTs

  1. x1 <- expr(class(!!data.frame(x = 10)))
  2. x1
  3. #> class(list(x = 10))
  4. eval(x1)
  5. #> [1] "data.frame"

这里你是不是觉得应该是“list1”?

5. Non-quoting

Base R has one function that implements quasiquotation: bquote(). It uses .() for unquoting:

6. … (dot-dot-dot)

  1. dfs <- list(
  2. a = data.frame(x = 1, y = 2),
  3. b = data.frame(x = 3, y = 4)
  4. )
  5. var <- "x"
  6. val <- c(4, 3, 9)
  7. # 如何合并一个list中的数据框
  8. dplyr::bind_rows(!!!dfs)
  9. #> x y
  10. #> 1 1 2
  11. #> 2 3 4
  12. tibble::tibble(!!var := val)
  13. #> # A tibble: 3 x 1
  14. #> x
  15. #> <dbl>
  16. #> 1 4
  17. #> 2 3
  18. #> 3 9

Row-binding multiple data frames is like unquote-splicing: we want to inline individual elements of the list into the call: