rlang::expr()
1 to capture code directlyrlang::enexpr()
to capture code indirectlycapture_it <- function(x) { # 'automatically quotes first argument'
enexpr(x)
}
capture_it(a + b + c)
#> a + b + c
#> [1] "" "x" "y"
ff <- fff <- f # Create two copies
ff$z <- 3 # Add an argument to one
fff[[2]] <- NULL # Remove an argument from another
f
#> f(x = 1, y = 2)
#> f(x = 1, y = 2, z = 3)
#> f(y = 2)
More on this next week!
lobstr::ast()
to inspect these code treesrlang::call2()
creates function call#> f1(f2("a", "b"), f3(1))
#> 1 + 2 * 3
!!
bang-bang - unquote operator
#> xx/yy
#> (x + x)/(y + y)
cv <- function(var) {
var <- enexpr(var) # Get user's expression
expr(sd(!!var) / mean(!!var)) # Insert user's expression
}
cv(x)
#> sd(x)/mean(x)
#> sd(x + y)/mean(x + y)
paste()
for building code
“You might think this is an esoteric concern, but not worrying about it when generating SQL code in web applications led to SQL injection attacks that have collectively cost billions of dollars.”
eval()
uses current environment if not setFor example…
string_math <- function(x) {
e <- env(
caller_env(),
`+` = function(x, y) paste(x, y),
`*` = function(x, y) strrep(x, y)
)
eval(enexpr(x), e)
}
cohort <- 9
string_math("Hello" + "cohort" + cohort)
#> [1] "Hello cohort 9"
#> [1] "dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---dslc is awesome---"
rlang::eval_tidy()
rather than eval()
Catch user input with enexpr()
…
But there’s a bug!
with2()
, but the expression likely refers to objects in the Global environmentwith2 <- function(df, expr) {
a <- 1000
eval_tidy(enexpr(expr), df)
}
df <- data.frame(x = 1:3)
a <- 10
with2(df, x + a)
#> [1] 1001 1002 1003
enquo()
instead of enexpr()
(with eval_tidy()
)with2 <- function(df, expr) {
a <- 1000
eval_tidy(enquo(expr), df)
}
df <- data.frame(x = 1:3)
a <- 10
with2(df, x + a)
#> [1] 11 12 13
“Whenever you use a data mask, you must always use
enquo()
instead ofenexpr()
.
This comes back in Chapter 20.
enquo()
is called)Here, the global environment
with2 <- function(df, expr) {
a <- 1000
eq <- enquo(expr)
message("with2() Parent/Calling environment: ")
print(rlang::caller_env())
message("with2() environment: ")
print(rlang::current_env())
message("Quosure details: ")
print(eq) # Print the details of the quosure
eval_tidy(eq, df)
}
a <- 10000
df <- data.frame(x = 1:3)
with2(df, x + a)
#> with2() Parent/Calling environment:
#> <environment: R_GlobalEnv>
#> with2() environment:
#> <environment: 0x0000018dede5ddd0>
#> Quosure details:
#> <quosure>
#> expr: ^x + a
#> env: global
#> [1] 10001 10002 10003
Here, the fun1()
environment
fun1 <- function(df) {
a <- 10
message("fun1() Parent/Calling environment: ")
print(rlang::caller_env())
message("fun1() environment: ")
print(rlang::current_env())
with2(df, x + a)
}
a <- 10000
df <- data.frame(x = 1:3)
fun1(df)
#> fun1() Parent/Calling environment:
#> <environment: R_GlobalEnv>
#> fun1() environment:
#> <environment: 0x0000018df3e748f8>
#> with2() Parent/Calling environment:
#> <environment: 0x0000018df3e748f8>
#> with2() environment:
#> <environment: 0x0000018df3ebc698>
#> Quosure details:
#> <quosure>
#> expr: ^x + a
#> env: 0x0000018df3e748f8
#> [1] 11 12 13