# When a tidyeval function captures an argument, it is wrapped in a
# formula and interpolated. f_quote() is a simple wrapper around
# arg_capture() and as such is the fundamental tidyeval
# function. It allows you to quote an expression and interpolate
# unquoted parts:
f_quote(foo(bar))
f_quote(!! 1 + 2)
f_quote(paste0(!! letters[1:2], "foo"))
# Alternatively you can interpolate a formula that is already
# constructed:
interp(~!! 1 + 2)
f <- ~paste0(!! letters[1:2], "foo")
interp(f)
# The !! operator is a syntactic shortcut for unquoting. However
# you need to be a bit careful with operator precedence. All
# arithmetic and comparison operators bind more tightly than `!`:
interp(x ~ 1 + !! (1 + 2 + 3) + 10)
# For this reason you should always wrap the unquoted expression
# with parentheses when operators are involved:
interp(x ~ 1 + (!! 1 + 2 + 3) + 10)
# Or you can use the explicit unquote function:
interp(x ~ 1 + UQ(1 + 2 + 3) + 10)
# Use !!! or UQS() if you want to add multiple arguments to a
# function It must evaluate to a list
args <- list(1:10, na.rm = TRUE)
interp(~mean(!!! args))
f_quote(mean( UQS(args) ))
# You can combine the two
var <- quote(xyz)
extra_args <- list(trim = 0.9, na.rm = TRUE)
f_quote(mean(UQ(var) , UQS(extra_args)))
interp(~mean(!!var , !!!extra_args))
# Unquoting is especially useful for transforming a captured
# expression:
expr <- quote(foo(bar))
expr <- expr_quote(inner(!! expr, arg1))
expr <- expr_quote(outer(!! expr, !!! lapply(letters[1:3], as.symbol)))
expr
# Quasiquotation of formulas is much more powerful. Formulas carry
# scope information about the inner expression inlined in the outer
# expression upon unquoting. Let's create a formula that quotes a
# symbol that only exists in a local scope (a child environment of
# the current environment):
f1 <- local({ foo <- "foo"; ~foo })
# You can evaluate that expression with f_eval():
f_eval(f1)
# But you can also inline it in another expression before
# evaluation:
f2 <- local({ bar <- "bar"; ~toupper(bar)})
f3 <- f_quote(paste(!!f1, !!f2, "!"))
f3
# f_eval() treats one-sided formulas like promises to be evaluated:
f_eval(f3)
# The formula-promise representation is necessary to preserve scope
# information and make sure objects are looked up in the right
# place. However, there are situations where it can get in the way.
# This is the case when you deal with non-tidy NSE functions that do
# not understand formulas. You can inline the RHS of a formula in a
# call thanks to the UQE() operator:
nse_function <- function(arg) substitute(arg)
var <- local(~foo(bar))
f_quote(nse_function(UQ(var)))
f_quote(nse_function(UQE(var)))
# This is equivalent to unquoting and taking the RHS:
f_quote(nse_function(!! f_rhs(var)))
# One of the most important old-style NSE function is the dollar
# operator. You need to use UQE() for subsetting with dollar:
var <- ~cyl
f_quote(mtcars$UQE(var))
# `!!`() is also treated as a shortcut. It is meant for situations
# where the bang operator would not parse, such as subsetting with
# $. Since that's its main purpose, we've made it a shortcut for
# UQE() rather than UQ():
var <- ~cyl
f_quote(mtcars$`!!`(var))
# Sometimes you would like to unquote an object containing a
# formula but include it as is rather than treating it as a
# promise. You can use UQF() for this purpose:
var <- ~letters[1:2]
f <- f_quote(list(!!var, UQF(var)))
f
f_eval(f)
# Note that two-sided formulas are never treated as fpromises:
f_eval(f_quote(a ~ b))
# Finally, you can also interpolate a closure's body. This is
# useful to inline a function within another. The important
# limitation is that all formal arguments of the inlined function
# should be defined in the receiving function:
other_fn <- function(x) toupper(x)
fn <- interp(function(x) {
x <- paste0(x, "_suffix")
!!! body(other_fn)
})
fn
fn("foo")
Run the code above in your browser using DataLab