class: center, middle, inverse, title-slide # Advanced R ## Chapter 19: Quasiquotation ### Jon Harmon --- # 19.1: Introduction * **Quotation** prevents evaluation * **Unquotation** re-allows evaluation * **Quasiquotation** allows you to mix the two * Useful (?) for metaprogramming * Let's see if that's true! --- # 19.2: Motivation ```r cement <- function(...) { args <- rlang::ensyms(...) paste( purrr::map(args, rlang::as_string), collapse = " " ) } ``` -- ```r canonical <- paste( letters[c(3, 15, 14, 20, 18, 9, 22, 5, 4)], collapse = "" ) cement( This, example, is, !!canonical ) ``` -- ``` ## [1] "This example is contrived" ``` --- # 19.2.1: Vocabulary * **Evaluated** arguments obey R's normal rules. * **Quoted** arguments are captured and processed in a special way. -- ```r # Evaluated mean(1:5) ``` ``` ## [1] 3 ``` ```r 1:5 ``` ``` ## [1] 1 2 3 4 5 ``` -- ```r # Quoted library(rlang) ``` ```r rlang #> Error: object 'rlang' not found ``` --- # 19.2.1: More Vocabulary * **quoting function** * "function that quotes 1 or more arguments" * non-standard evaluation * NSE --- # 19.2.1: More Vocabulary * **quoting function** `==` <br> "function that quotes 1 or more arguments" `==` <br> non-standard evaluation `==` <br> NSE -- **Kinda.** Technically NSE is about arguments only. --- # 19.2.2: Exercises: Base <code class ='r hljs remark-code'>library(MASS)<br><br>mtcars2 <- subset(mtcars, cyl == 4)<br><br>with(mtcars2, sum(vs))<br>sum(mtcars2$am)</code> --- # 19.2.2: Exercises: Base <code class ='r hljs remark-code'>library(<span style='background-color:#ffff7f'>MASS</span>)<br><br>mtcars2 <- subset(mtcars, <span style='background-color:#ffff7f'>cyl == 4</span>)<br><br>with(mtcars2, <span style='background-color:#ffff7f'>sum(vs)</span>)<br>sum(mtcars2$<span style='background-color:#ffff7f'>am</span>)</code> --- # 19.2.2: Exercises: Tidyverse <code class ='r hljs remark-code'>library(dplyr, warn.conflicts = FALSE)<br>library(ggplot2, warn.conflicts = FALSE)<br><br>byCyl <- mtcars %>%<br> group_by(cyl) %>%<br> summarise(mean = mean(mpg), .groups = "drop_last")<br><br>ggplot(byCyl, aes(cyl, mean)) + geom_point()</code> --- # 19.2.2: Exercises: Tidyverse <code class ='r hljs remark-code'>library(<span style='background-color:#ffff7f'>dplyr</span>, warn.conflicts = FALSE)<br>library(<span style='background-color:#ffff7f'>ggplot2</span>, warn.conflicts = FALSE)<br><br>byCyl <- mtcars %>%<br> group_by(<span style='background-color:#ffff7f'><span style='background-color:#ffff7f'>cyl</span></span>) %>%<br> summarise(<span style='background-color:#ffff7f'>mean</span> = <span style='background-color:#ffff7f'><span style='background-color:#ffff7f'>mean</span>(mpg)</span>, .groups = "drop_last")<br><br>ggplot(byCyl, aes(<span style='background-color:#ffff7f'><span style='background-color:#ffff7f'>cyl</span></span>, <span style='background-color:#ffff7f'>mean</span>)) + geom_point()</code> Note: The first `mean` shouldn't be highlighted, but I'm struggling with {flair} 😦 --- # 19.3: Quoting * "We'll need a pair of functions" (~6 pairs, including base) * **directly supplied** vs * **indirectly supplied** --- # 19.3.1: Capturing expressions * `{rlang}` * `expr` vs `enexpr` * `exprs` vs `enexprs` * `quote directly` vs `quote the thing in the calling environment` --- # 19.3.1: Capturing expressions ```r testing <- "foo" testing2 <- testing rlang::expr(testing) ``` ``` ## testing ``` ```r rlang::expr(testing2) ``` ``` ## testing2 ``` -- ```r rlang::enexpr(testing) ``` ``` ## [1] "foo" ``` --- # 19.3.1: Capturing expressions ```r catch_it <- function(x) rlang::enexpr(x) catch_it(testing) ``` ``` ## testing ``` ```r catch_it(testing2) ``` ``` ## testing2 ``` --- # 19.3.1: Capturing expressions * `exprs` and `enexprs` capture a list of `expr` or `enexpr` ```r rlang::exprs(x = testing, y = testing2, z = paste(testing, testing2)) ``` ``` ## $x ## testing ## ## $y ## testing2 ## ## $z ## paste(testing, testing2) ``` ```r # shorthand for # list( # x = expr(testing), # y = expr(testing2), # z = expr(paste(testing, testing2)) # ) ``` --- # 19.3.1: Capturing expressions * The pairs are always **console_version** + ***en*console_version** * Aka ***no-en*-function_version** + **function_version** --- # 19.3.2: Capturing symbols * `sym` and `syms` make sure the thing they're capturing is symbol or character * `ensym` and `ensyms` in functions --- # 19.3.3: With base R * `expr` is to `quote` * as `enexpr` is to `substitute` ("normal" usage) * as `exprs` is to `alist` * as `enexprs(...)` is to `as.list(substitute(...()))` ??? * This is undocumented and kinda insane and I don't think I'll ever use it --- # 19.3.3: With base R * A `substitute` example: `library` ```r library_meat <- function(package) { as.character(substitute(package)) # Without as.character, it returns a name } library_meat(rlang) ``` ``` ## [1] "rlang" ``` --- # 19.3.4: Substitution * `substitute` can be used to... substitute ```r f4 <- function(x) substitute(x * 2) f4(a + b + c) ``` ``` ## (a + b + c) * 2 ``` ```r f4(whatever) ``` ``` ## whatever * 2 ``` --- # 19.3.4: Substitution * `substitute` can be used to... substitute * It's confusing, so make it explicit. * I tried to implement this and found what I really wanted was to unquote part of the expression... ```r f4b <- function(x) substitute(x, list(a = 1, b = 2, c = 3)) f4b(a + b + c) # Not what I meant! ``` ``` ## x ``` ```r f4b(literally - anything) ``` ``` ## x ``` --- # 19.4: Unquoting * quasiquotation means you can unquote selectively * Base R doesn't allow you to selectively unquote (mostly) * Unquoting means "replace the object with the thing it represents" --- # 19.4.1: Unquoting one argument * The confusingly beautiful heart of quasiquotation: **!!** * Use it to unquote one thing ```r x <- expr(-1) y <- "a character" rlang::expr(f(!!x, y)) ``` ``` ## f(-1, y) ``` ```r rlang::expr(f(x, !!y)) ``` ``` ## f(x, "a character") ``` ```r rlang::expr(mean(1:3) + mean(4:6)) ``` ``` ## mean(1:3) + mean(4:6) ``` ```r rlang::expr(!!mean(1:3) + !!mean(4:6)) ``` ``` ## 2 + 5 ``` --- # 19.4.2: Unquoting a function * Now we start to move toward metaprogramming ```r replace_f <- function(func) { f <- rlang::enexpr(func) rlang::expr((!!f)(x, y)) # Would be better if we used the actual formals... } replace_f(mean) ``` ``` ## mean(x, y) ``` ```r replace_f(rlang::enexpr) ``` ``` ## rlang::enexpr(x, y) ``` --- # 19.4.5: Unquoting many arguments * `!!!` is to `!!` as `exprs` is to `expr` * **"unquote-splice"** because it **unquotes** the list then **splices** them in as if they were all separate arguments ```r multi_arg <- list(a = 1, b = 2, c = "other") rlang::expr(f(!!!multi_arg, another_arg)) ``` ``` ## f(a = 1, b = 2, c = "other", another_arg) ``` --- # 19.4.6: The polite fiction of !! * `!!` and `!!!` don't actually exist ```r rlang::`!!` ``` ``` ## function (x) ## { ## abort("`!!` can only be used within a quasiquoted argument") ## } ## <bytecode: 0x000000001347dcc8> ## <environment: namespace:rlang> ``` ```r rlang::`!!!` ``` ``` ## function (x) ## { ## abort("`!!!` can only be used within a quasiquoted argument") ## } ## <bytecode: 0x0000000016ad5590> ## <environment: namespace:rlang> ``` --- # 19.6 ... (dot-dot-dot) * This section is the leftovers, it isn't about `...` per se * `:=` "colon-equals" or "digested is" (how I think of it) * Another fiction to trick R into working how we want * LHS of `=` can't be evaluated, so we trick R ```r var <- "my_var_name" val <- 1:3 tibble::tibble(!!var := val) ``` ``` ## # A tibble: 3 x 1 ## my_var_name ## <int> ## 1 1 ## 2 2 ## 3 3 ``` --- # 19.6.2: exec() * This feels out-of-place in this chapter * `rlang::exec` is similar to `base::do.call` * Allows you to use unquoting to do fancy things ```r params <- list(na.rm = TRUE, trim = 0.1) func <- "mean" rlang::exec(func, x = 1:10, !!!params) ``` ``` ## [1] 5.5 ``` --- # 19.7: Case studies * I'd like a function like `stringr::str_replace(string, pattern, replacement)` * But instead, `funky::fn_replace(function, pattern, replacement)` * I don't want the user to have to use any `rlang` or `quote` or `substitute` (etc)