library(purrr) instead of library("purrr")Non-standard evaluation (NSE) ➜ not telling R where an object comes from?
mtcars$cyl v/s cyl
Non-standard evaluation is commonly used to describe the behaviour of R functions, but…
“Specifically, this book focuses on tidy evaluation. Tidy evaluation is implemented in the
rlangpackage, and I’ll userlangextensively in these chapters. This will allow you to focus on the big ideas, without being distracted by the quirks of implementation that arise from R’s history”
Use rlang::expr() to capture code directly
Use rlang::enexpr() to capture code indirectly
And let’s take a look…
lobstr::ast() to inspect these code trees#> █─mean
#> └─x = vct
#> █─mean
#> ├─x = vct
#> ├─trim = 0.1
#> └─na.rm = TRUE
#> █─round
#> ├─x = █─mean
#> │ ├─x = vct
#> │ ├─trim = 0.1
#> │ └─na.rm = TRUE
#> └─digits = 0
rlang::call2()!! (“bang-bang”) - unquote operator{{ }} (“curly-curly”) - embrace operator (introduced after this book was published, equivalent to !!enquo())call2() constructs function callsrlang::call2() constructs a function call from its components ➜ the function to call, and the arguments to call it with.call2() can build complex calls#> mean(x = 1:100, trim = 0.1, na.rm = TRUE)
#> round(x = mean(x = 1:100, trim = 0.1, na.rm = TRUE), digits = 0)
!! injects expressions!! (“bang-bang”) - unquote operator
!! user input is not inserted#> sd(var)/mean(var)
It doesn’t work.
Avoid paste() for building code ➜ problems with non-syntactic names and precedence among expressions
“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 set#> [1] "Hello cohort 10"
#> [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---dslc is awesome---"
rlang::eval_tidy() rather than eval()We also can catch user input with enexpr()…
But there’s a bug!
Bug ➜
with2(), butenquo() creates a quosureenquo() instead of enexpr() (with eval_tidy())enquo() with data masks“Whenever you use a data mask, you must always use
enquo()instead ofenexpr()”.
enquo() is called)enquo() captures the calling environmentwith2 <- 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)#> <environment: R_GlobalEnv>
#> <environment: 0x00000208278b93f0>
#> <quosure>
#> expr: ^x + a
#> env: global
#> [1] 10001 10002 10003
enquo(), the wrong environment is captured#> <environment: R_GlobalEnv>
#> <environment: 0x00000208287b37a8>
#> <environment: 0x00000208287b37a8>
#> <environment: 0x00000208287a4970>
#> <quosure>
#> expr: ^x + a
#> env: 0x00000208287b37a8
#> [1] 11 12 13
rlang::expr() and rlang::enexpr().lobstr::ast().rlang::call2().!!.eval() or eval_tidy().rlang::enquo() captures user input in a quosure.