+ - 0:00:00
Notes for current slide
Notes for next slide

Advanced R

Chapter 19: Quasiquotation

Jon Harmon

1 / 35

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!
2 / 35

19.2: Motivation

cement <- function(...) {
args <- rlang::ensyms(...)
paste(
purrr::map(args, rlang::as_string),
collapse = " "
)
}
3 / 35

19.2: Motivation

cement <- function(...) {
args <- rlang::ensyms(...)
paste(
purrr::map(args, rlang::as_string),
collapse = " "
)
}
canonical <- paste(
letters[c(3, 15, 14, 20, 18, 9, 22, 5, 4)],
collapse = ""
)
cement(
This, example, is, !!canonical
)
4 / 35

19.2: Motivation

cement <- function(...) {
args <- rlang::ensyms(...)
paste(
purrr::map(args, rlang::as_string),
collapse = " "
)
}
canonical <- paste(
letters[c(3, 15, 14, 20, 18, 9, 22, 5, 4)],
collapse = ""
)
cement(
This, example, is, !!canonical
)
## [1] "This example is contrived"
5 / 35

19.2.1: Vocabulary

  • Evaluated arguments obey R's normal rules.
  • Quoted arguments are captured and processed in a special way.
6 / 35

19.2.1: Vocabulary

  • Evaluated arguments obey R's normal rules.
  • Quoted arguments are captured and processed in a special way.
# Evaluated
mean(1:5)
## [1] 3
1:5
## [1] 1 2 3 4 5
7 / 35

19.2.1: Vocabulary

  • Evaluated arguments obey R's normal rules.
  • Quoted arguments are captured and processed in a special way.
# Evaluated
mean(1:5)
## [1] 3
1:5
## [1] 1 2 3 4 5
# Quoted
library(rlang)
rlang
#> Error: object 'rlang' not found
8 / 35

19.2.1: More Vocabulary

  • quoting function
  • "function that quotes 1 or more arguments"
  • non-standard evaluation
  • NSE
9 / 35

19.2.1: More Vocabulary

  • quoting function ==
    "function that quotes 1 or more arguments" ==
    non-standard evaluation ==
    NSE
10 / 35

19.2.1: More Vocabulary

  • quoting function ==
    "function that quotes 1 or more arguments" ==
    non-standard evaluation ==
    NSE

Kinda. Technically NSE is about arguments only.

11 / 35

19.2.2: Exercises: Base

library(MASS)

mtcars2 <- subset(mtcars, cyl == 4)

with(mtcars2, sum(vs))
sum(mtcars2$am)

12 / 35

19.2.2: Exercises: Base

library(MASS)

mtcars2 <- subset(mtcars, cyl == 4)

with(mtcars2, sum(vs))
sum(mtcars2$am)

13 / 35

19.2.2: Exercises: Tidyverse

library(dplyr, warn.conflicts = FALSE)
library(ggplot2, warn.conflicts = FALSE)

byCyl <- mtcars %>%
  group_by(cyl) %>%
  summarise(mean = mean(mpg), .groups = "drop_last")

ggplot(byCyl, aes(cyl, mean)) + geom_point()

14 / 35

19.2.2: Exercises: Tidyverse

library(dplyr, warn.conflicts = FALSE)
library(ggplot2, warn.conflicts = FALSE)

byCyl <- mtcars %>%
  group_by(cyl) %>%
  summarise(mean = mean(mpg), .groups = "drop_last")

ggplot(byCyl, aes(cyl, mean)) + geom_point()

Note: The first mean shouldn't be highlighted, but I'm struggling with {flair} 😦

15 / 35

19.3: Quoting

  • "We'll need a pair of functions" (~6 pairs, including base)
    • directly supplied vs
    • indirectly supplied
16 / 35

19.3.1: Capturing expressions

  • {rlang}
    • expr vs enexpr
    • exprs vs enexprs
    • quote directly vs quote the thing in the calling environment
17 / 35

19.3.1: Capturing expressions

testing <- "foo"
testing2 <- testing
rlang::expr(testing)
## testing
rlang::expr(testing2)
## testing2
18 / 35

19.3.1: Capturing expressions

testing <- "foo"
testing2 <- testing
rlang::expr(testing)
## testing
rlang::expr(testing2)
## testing2
rlang::enexpr(testing)
## [1] "foo"
19 / 35

19.3.1: Capturing expressions

catch_it <- function(x) rlang::enexpr(x)
catch_it(testing)
## testing
catch_it(testing2)
## testing2
20 / 35

19.3.1: Capturing expressions

  • exprs and enexprs capture a list of expr or enexpr
rlang::exprs(x = testing, y = testing2, z = paste(testing, testing2))
## $x
## testing
##
## $y
## testing2
##
## $z
## paste(testing, testing2)
# shorthand for
# list(
# x = expr(testing),
# y = expr(testing2),
# z = expr(paste(testing, testing2))
# )
21 / 35

19.3.1: Capturing expressions

  • The pairs are always console_version + enconsole_version
  • Aka no-en-function_version + function_version
22 / 35

19.3.2: Capturing symbols

  • sym and syms make sure the thing they're capturing is symbol or character
  • ensym and ensyms in functions
23 / 35

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
24 / 35

19.3.3: With base R

  • A substitute example: library
library_meat <- function(package) {
as.character(substitute(package)) # Without as.character, it returns a name
}
library_meat(rlang)
## [1] "rlang"
25 / 35

19.3.4: Substitution

  • substitute can be used to... substitute
f4 <- function(x) substitute(x * 2)
f4(a + b + c)
## (a + b + c) * 2
f4(whatever)
## whatever * 2
26 / 35

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...
f4b <- function(x) substitute(x, list(a = 1, b = 2, c = 3))
f4b(a + b + c) # Not what I meant!
## x
f4b(literally - anything)
## x
27 / 35

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"
28 / 35

19.4.1: Unquoting one argument

  • The confusingly beautiful heart of quasiquotation: !!
  • Use it to unquote one thing
x <- expr(-1)
y <- "a character"
rlang::expr(f(!!x, y))
## f(-1, y)
rlang::expr(f(x, !!y))
## f(x, "a character")
rlang::expr(mean(1:3) + mean(4:6))
## mean(1:3) + mean(4:6)
rlang::expr(!!mean(1:3) + !!mean(4:6))
## 2 + 5
29 / 35

19.4.2: Unquoting a function

  • Now we start to move toward metaprogramming
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)
replace_f(rlang::enexpr)
## rlang::enexpr(x, y)
30 / 35

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
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)
31 / 35

19.4.6: The polite fiction of !!

  • !! and !!! don't actually exist
rlang::`!!`
## function (x)
## {
## abort("`!!` can only be used within a quasiquoted argument")
## }
## <bytecode: 0x000000001347dcc8>
## <environment: namespace:rlang>
rlang::`!!!`
## function (x)
## {
## abort("`!!!` can only be used within a quasiquoted argument")
## }
## <bytecode: 0x0000000016ad5590>
## <environment: namespace:rlang>
32 / 35

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
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
33 / 35

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
params <- list(na.rm = TRUE, trim = 0.1)
func <- "mean"
rlang::exec(func, x = 1:10, !!!params)
## [1] 5.5
34 / 35

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)
35 / 35

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!
2 / 35
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow