class: center, middle, inverse, title-slide # Non-Standard Evaluation ## and Metaprogramming in R ###
Janus Valberg-Madsen
### 2018-10-02 @ Aalborg R User Group --- ## My background with programming and R - Tampered a little with Android (Java) during my STX years (2009-2012) - Introduced to Python for 2nd semester project at AAU (spring 2014) - Introduced to R for for 5th semester course in numerical optimisation (autumn 2015) - Used R extensively since then, both for university projects and as part of my former student assistant job at NEAS Energy A/S (2016-2018) --- ## This talk in a nutshell 1. The structure of the R language 2. How to metaprogram in R 3. Applications ??? 1. In the first part I will dive into the inner workings of R and show you the parts that enable metaprogramming 2. In the second part, I will show you an overview the tools you need and what they do 3. In the last part, I will show you some situations in which metaprogramming comes in handy --- layout: false class: center, middle layout: true --- # What is non-standard evaluation? ??? Let's jump right into the central question for this talk: what is NSE? The quick answer is: > Functions are said to use NSE when they evaluate one or more of their arguments in a non-standard way. I don't suppose that really answers the question, though. Given this answer, what then is standard evaluation? Another, slightly more informative way of putting it is that > NSE is what we call it when functions use _metaprogramming_ I have titled this talk "Non-Standard Evaluation **and** Metaprogramming in R", but in truth, they aren't really seperate concepts. NSE seems to be a term originating from R circles, but metaprogramming is a paradigm that's certainly not exclusive to R. So, [go to next slide] --- # What is metaprogramming? ??? Answer: it's the ability to compute on the language itself According to Wikipedia: > Metaprogramming is a programming technique in which computer programs have the ability to treat programs as their data. > It means that a program can be designed to read, generate, analyse or transform other programs, and even modify itself while running. Metaprogramming traces its roots back to the 1940s, where philosopher and logician Willard van Orman Quine introduces the concept of _quasiquotation_ as a linguistic device in formal languages (more on quasiquotation later) The first programming language to support MP was Lisp in the 1970s. Some of you likely knows some Lisp, especially if you use Emacs. Lisp went **all in** on metaprogramming, making no distinction between code and data. The computer scientist Paul Graham wrote in his essay "Revenge of the Nerds": > _Lisp looks strange not so much because it has a strange syntax as because it has no syntax; you express programs directly in the parse trees that get built behind the scenes when other languages are parsed, and these trees are made of lists, which are Lisp data structures._ This is not a talk on Lisp, but R does inherit many ideas from Lisp, such as _parse trees_ and _lexical scoping_ both of which we'll see in a few slides. --- layout: false name: r-structure class: center, middle, inverse background-image: url(./img/black-50.png), url(./img/code.jpg) background-size: cover # The structure of the R language ??? So let's dive into the meat of it all: how R works Before we can compute on the language, we have to understand the language we want to compute on. To quote Carl Sagan, > _If you wish to make apple pie from scratch, you must first invent the universe_ So we ask ourselves: **how does R work?** --- ```r x <- seq(0, 2 * pi, length.out = 100) plot(x, sin(x), type = "l") ``` ??? To answer that, let's look at an example. Here is an R function which uses the _code_ given for its arguments in addition to their values -- <img src="figs/plot-labels-1.svg" style="display: block; margin: auto;" /> ??? Just a regular old plot using the base R `plot` function -- How does `plot` know how to label the axes? ??? - I didn't pass any label-related arguments - So where did these labels come from? - Answer: **from the function arguments themselves** --- layout: true name: substitute ??? Lets take a look under the hood of `plot.default` Tip: inspect source code of function with - `body(fun)` - `fun` in console (without parentheses) - In RStudio: place cursor in function name and press F2 As we can see from the argument list, you can pass axis labels explicitly, but you seemingly don't have to. --- template: substitute ```r > plot.default ``` ```r function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL, panel.last = NULL, asp = NA, ...) { localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...) localBox <- function(..., col, bg, pch, cex, lty, lwd) box(...) localWindow <- function(..., col, bg, pch, cex, lty, lwd) plot.window(...) localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...) xlabel <- if (!missing(x)) deparse(substitute(x)) ylabel <- if (!missing(y)) deparse(substitute(y)) xy <- xy.coords(x, y, xlabel, ylabel, log) # ... ``` --- count: false template: substitute ```r > plot.default ``` ```r function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL, panel.last = NULL, asp = NA, ...) { localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...) localBox <- function(..., col, bg, pch, cex, lty, lwd) box(...) localWindow <- function(..., col, bg, pch, cex, lty, lwd) plot.window(...) localTitle <- function(..., col, bg, pch, cex, lty, lwd) title(...) xlabel <- if (!missing(x)) * deparse(substitute(x)) ylabel <- if (!missing(y)) * deparse(substitute(y)) xy <- xy.coords(x, y, xlabel, ylabel, log) # ... ``` ??? These are the lines that produce the axis labels -- - `deparse`: turns unevaluated expressions into strings ??? - As the name implies, it does the opposite of parsing: expr -> string -- - `substitute`: returns the *parse tree* for the expression ??? - Expression? But `x` and `y` are just numeric vectors, right? --- name: promises class: center, middle, inverse background-image: url(./img/black-25.png), url(./img/promise.jpg) background-size: cover layout: false # Promises ??? Wrong. `x` and `y` are _promises_ Here we come across one of the many reasons why I like R so much ― it reminds me of myself: - It is _lazy_ - It makes _promises_ to evaluate your function arguments - but it won't _actually_ do it, until it is absolutely necessary What _is_ a promise? From the R language definition: - Promise objects are part of R’s lazy evaluation mechanism - They contain three slots: a value, an expression, and an environment - When a function is called the arguments are matched and then each of the formal arguments is bound to a promise - The expression that was given for that formal argument and a pointer to the environment the function was called from are stored in the promise --- ## Function arguments are lazy ```r error_msg <- "Uh oh! You friccin moron. You just got BEANED!!!" f <- function(arg) { print("Nothing to see here...") } f(stop(error_msg)) ``` ``` ## [1] "Nothing to see here..." ``` ??? Here's an example of lazy evaluation in action: - The function `f` takes an argument `arg`, but doesn't use it for anything - Therefore, `arg` isn't ever evaluated, and we don't get an error -- ```r f <- function(arg) { arg print("Nothing to see here...") } f(stop(error_msg)) ``` ``` ## Error in f(stop(error_msg)): Uh oh! You friccin moron. You just got BEANED!!! ``` ??? However, if the argument is used for _anything_, it forces evaluation, and we get an error --- layout: true ## Calls are represented by abstract syntax trees (AST) --- .pull-left[ ```r e1 <- quote(y <- 2 * x) e2 <- quote(`<-`(y, `*`(2, x))) identical(e1, e2) ``` ``` ## [1] TRUE ``` ] ??? Now we get to those aforementioned _abstract syntax trees_. Here, we have the expression `y <- 2 * x`, which is _captured_ instead of evaluated via `quote()` (more on this later). Then we have another expression that may look different, but is actually the same. --- count: false .pull-left[ ```r e1 <- quote(y <- 2 * x) e2 <- quote(`<-`(y, `*`(2, x))) identical(e1, e2) ``` ``` ## [1] TRUE ``` ```r mode(e1) ``` ``` ## [1] "call" ``` .pull-left[ ```r as.list(e1) ``` ``` ## [[1]] ## `<-` ## ## [[2]] ## y ## ## [[3]] ## 2 * x ``` ] .pull-right[ ```r as.list(e2) ``` ``` ## [[1]] ## `<-` ## ## [[2]] ## y ## ## [[3]] ## 2 * x ``` ] ] ??? What happens is that expressions are turned into _parse trees_ by the R interpreter, i.e. the structure it uses to evaluate the expression. `e2` is on so-called _standard form_, and when we type in `e1`, under the hood it gets turned into standard form. `e1` is a `call` object in R, and if we break it into atoms, we see that it's represented exactly the same as `e2`. Tip: in RStudio, type `View(e1)` to interactively and recursively browse the atoms of a call object **[show this]** -- .pull-right[ <img src="figs/simple-ast.svg" width="100%" style="display: block; margin: auto;" /> ] ??? Here is a graphical representation of what's going on, called an _abstract syntax tree (AST)_. - The orange squares represent calls; first child is the function being called, subsequent children are arguments. - Purple, rounded boxes represent symbols; symbols refer to R objects, it's what you type before `<-`, e.g. `y` - Black boxes are syntactic literals; atomic values (logical, numeric, character, etc.) --- layout: true ## Everything is a call ??? Everything in the language is a call (or a symbol or a literal) Looking at the values of these two expressions, they're obviously identical. They both simply evaluate to a numeric 1. However, the expressions themselves are different from each other. This is because everything in R (that isn't a symbol or literal) is a function, including all the delimiters. Parentheses and braces are `.Primitive` functions in R, and act as identity functions (with different printing rules). --- .pull-left[ ```r identical(1, ({1})) ``` ``` ## [1] TRUE ``` ] --- count: false .pull-left[ ```r identical(1, ({1})) ``` ``` ## [1] TRUE ``` ```r e1 <- quote(1) e2 <- quote(({1})) identical(e1, e2) ``` ``` ## [1] FALSE ``` ] --- count: false .pull-left[ ```r identical(1, ({1})) ``` ``` ## [1] TRUE ``` ```r e1 <- quote(1) e2 <- quote(({1})) identical(e1, e2) ``` ``` ## [1] FALSE ``` .pull-left[ ```r typeof(e1) ``` ``` ## [1] "double" ``` ] .pull-right[ ```r typeof(e2) ``` ``` ## [1] "language" ``` ] ] -- .pull-right[ <img src="figs/parens-ast.svg" width="55%" style="display: block; margin: auto;" /> ] --- .pull-left[ ```r for (i in 1:10) { i^2 } ``` <img src="figs/for-ast.svg" width="100%" style="display: block; margin: auto;" /> ] .pull-right[ ```r if (x >= 0) { sqrt(x) } else { 0 } ``` <img src="figs/if-ast.svg" width="100%" style="display: block; margin: auto;" /> ] ??? The same goes for more involved control structures. Here are the ASTs for a simple `for` loop and an `if-else` block. They are no different than they would be for any other function; - First child is the function name (`for`, `if`) - Subsequent children are function arguments + looping variable, its range, and the expression to evaluate + condition, expression if `TRUE`, expression if `FALSE` As an aside leading back to Lisp: Once you get the hang of ASTs, you basically know Lisp In Emacs [Lisp Interaction]: ```lisp (set 'y '(* 2 x)) (set 'x 4) (eval y) ``` `C-u C-x C-e` to eval and insert value --- layout: false class: center, middle ## Environments ??? Up until now, I've mentioned _environments_ a lot. They are, put shortly, kinda like lists, but - have unique names - have unordered elements - have parent environments - are _not_ copied when modified --- ## Lexical scoping ??? Lexical scoping referes to how R looks up values for symbols you use in function calls. This is another thing R has inherited from Lisp. It looks up symbols based on how functions were nested when they were created (not called) When a function is called 1. R looks up its arguments in the environment where the function is defined 2. If it doesn't find it, it goes to the parent environment 3. If it ends up in the `emptyenv`, the ancestor of all environments, it throws an error -- .pull-left[ ```r f1 <- function() { b <- 2 f2 <- function() { c <- 3 f3 <- function() { a + b + c } f3() } f2() } ``` ] ??? Here is an example with three nested functions -- .pull-right[ ```r f1() ``` ``` ## Error in f3(): object 'a' not found ``` ```r a <- 1 f1() ``` ``` ## [1] 6 ``` ] ??? When we call it, R starts looking up symbols from the innermost function environment. At first, since it doesn't find `a`, it throws an error. But if we define `a` in the global environment, it returns a value. --- layout: false name: howto class: middle, center, inverse background-image: url(./img/black-50.png), url(./img/typing.jpg) background-size: cover # How to metaprogram in R ??? In this section of the talk, we will take a look at the tools available for taking advantage of the lazy nature of R --- background-image: url(./img/white-85.png), url(./img/hadley.jpg) background-position: left bottom background-size: 450px ## The rlang package ```r # install.packages("rlang") library(rlang) ``` .pull-left[ ### Pros - Consistent - Good with `tidyverse` ] .pull-right[ ### Cons - Another dependency ] ??? It's a question you ask yourself every time you develop an R package: > Should I add a dependency that makes it easy or try to make it work with `base`? - Base R does have some built-in capability of non-standard evaluation (`quote`, `substitute`, etc.) - But I will use `rlang` (from the `tidyverse` suite), because: + it's recent + it's consistent + it plays very well with other `tidyverse` packages --- layout: true name: quasiquotation ## Quasiquotation --- ??? We return to Orman Quine's concept of _quasiquotation_, which plays a central role in metaprogramming. There are two sides to quasiquatation: - Quotation - Unquotation --- count: false name: quotation .left-column[ ### Quotation ] .right-column[ - .rb[`expr()`] : capture an expression _exactly as typed_ - .rb[`exprs()`] : create a list of expressions .pull-left[ ```r expr(y <- 2 * x) ``` ``` ## y <- 2 * x ``` ```r f1 <- function(x) expr(x) f1(y <- 2 * x) ``` ``` ## x ``` ] .pull-right[ ```r exprs(x = x^2, y = y^3) ``` ``` ## $x ## x^2 ## ## $y ## y^3 ``` ] ] ??? Quotation is the act of capturing an unevaluated expression. Capturing the AST of an expression associated with an argument is the core of NSE. `rlang` provides several functions for this task: `expr`/`exprs`, in Hadley's own terminology, capture the _developer's_ expressions. This means that the literal code you type between the parentheses is what is quoted; using it to quote an argument inside a function will return the argument name. --- .left-column[ ### Quotation ] .right-column[ - .rb[`enexpr()`] : capture a user-supplied expression - .rb[`enexprs()`] : capture multiple user-supplied expressions .pull-left[ ```r f1 <- function(x) enexpr(x) f1(y <- 2 * x) ``` ``` ## y <- 2 * x ``` ] .pull-right[ ```r f2 <- function(x, y) enexprs(x, y) f2(x^2, y^3) ``` ``` ## [[1]] ## x^2 ## ## [[2]] ## y^3 ``` ] ] ??? Most of the time, we probably don't want to quote our own input, but our users' instead. `enexpr`/`enexprs` capture the _user's_ expressions. This means that it captures the expression associated with a function argument _as the user supplied it_ instead of how you, the developer, coded it. --- .left-column[ ### Quotation ] .right-column[ - .rb[`sym()`], .rb[`syms()`] : turn string(s) into symbols - .rb[`ensym()`], .rb[`ensyms()`] : variants of `enexpr[s]` for strings OR bare symbols .pull-left[ ```r sym("x") ``` ``` ## x ``` ```r f1 <- function(x) sym(x) f1("y") ``` ``` ## y ``` ] .pull-right[ ```r f2 <- function(...) ensyms(...) f2("x", y) ``` ``` ## [[1]] ## x ## ## [[2]] ## y ``` ```r exists("y") ``` ``` ## [1] FALSE ``` ] ] ??? As I mentioned in the pros of `rlang`, it's syntactically consistent, and many functions exists in plural and en-prefixed variants These functions differ from `[en]expr[s]` in that their arguments must correspond to symbols, atoms of expressions, rather than whole expressions. `sym`/`syms` are for turning strings into quoted symbols; useful for `tidyverse` programming (we'll get to that later) `ensym`/`ensyms` perhaps takes a little more explaining: they check that the captured expression is _either_ a string or a symbol - If string: converts to symbol - If symbol: leaves as symbol - Error otherwise --- .left-column[ ### Quotation ] .right-column[ - .rb[`quo()`], .rb[`quos()`] : like `expr[s]`, but also captures the current environment - .rb[`enquo()`], .rb[`enquos()`] : like `enexpr[s]`, but also captures the calling environment ```r f <- function() { foo <- 10 quo(foo * 2) } f() ``` ``` ## <quosure> ## expr: ^foo * 2 ## env: 0x5a7c5f0 ``` ] ??? Quosures (quote + closure) form the basis of the so-called _tidy evaluation_ framework (which we'll get to in a second). They are implemented as a subclass of onesided formulas. Quosures expand on the "regular" quoting functions by not only capturing expressions, but also their context, i.e. the environments from which they came. The example shown here is not very interesting until we get to [Evaluation](#evaluation). --- name: unquoation .left-column[ ### Quotation ### Unquotation ] .right-column[ - .rb[`!!`] : (unquote) unquotes its argument (single expression) - .rb[`!!!`] : (unquote-splice) unquotes and splices its argument (list of expressions) .pull-left[ ```r x <- expr(a + b + c) expr(f(!!x, y)) ``` ``` ## f(a + b + c, y) ``` ```r xs <- exprs(a + b, c + d) expr(f(!!!xs)) ``` ``` ## f(a + b, c + d) ``` ] .pull-right[ ```r f <- function(x) { x <- enexpr(x) expr(!!x + y) } f(my_var) ``` ``` ## my_var + y ``` ] ] ??? Unquotation is related to evaluation, but with an important distinction: Unquoting lets us selectively evaluate parts of a quoted expression. Using this technique, we can form complex ASTs from simpler expressions. To put it simply, the unquote and unquote-splice _replace_ nodes in the ASTs. In Lisp: ```lisp (set 'snue '(bar baz)) `(foo ,snue) ;;> (foo (bar baz)) `(foo ,@snue) ;;> (foo bar baz) ``` --- layout: false name: evaluation ## Evaluation ??? Typically, you quote code because you want to either do something with the AST, run the code in another context, or (often) both. -- - .rb[`eval_bare()`] or simply .rb[`base::eval()`] : evaluate quoted expression - .rb[`eval_tidy()`] : evaluate quosure ??? `rlang` provides - `eval_bare` as a lower-level version of `base::eval`, which just evaluate their argument, optionally specifying an environment - `eval_tidy` for evaluation the special quosures, who as we saw "remember" the environment in which they were defined -- .pull-left[ ```r foo <- 5 f1 <- function() { foo <- 10 expr(foo * 2) } my_expr <- f1() my_expr ``` ``` ## foo * 2 ``` ```r eval_bare(my_expr) ``` ``` ## [1] 10 ``` ] .pull-right[ ```r foo <- 5 f2 <- function() { foo <- 10 quo(foo * 2) } my_quo <- f2() my_quo ``` ``` ## <quosure> ## expr: ^foo * 2 ## env: 0x5441120 ``` ```r eval_tidy(my_quo) ``` ``` ## [1] 20 ``` ] ??? These examples show the difference between regular evaluation and tidy evaluation. In the `expr` example to the left, `my_expr` only carries with it information about the expression it represent, and not its context. As such, when we evaluate it, the looks up symbols in the current environment, here the global environment, and finds `foo = 5`. In the `quo` example, however, `my_quo` additionally includes the context from its creation, i.e. the _execution_ environment. It therefore finds `foo = 10` from within the scope of `f2` and uses that, even though we also have `foo` in the global environment. --- layout: false name: applications class: center, middle, inverse # Applications ??? Time to look at some examples. --- class: center, middle background-image: url(img/white-85.png), url(img/hadley.jpg) background-size: cover ## Programming with tidyverse ??? NSE is almost synonymous with `tidyverse`, and I'd wager it's where most of us first hears about it. It's a common problem when you want to program with `tidyverse` packages: they are made for interactive use and uses NSE for syntactic convenience. This makes life difficult for us as developers, however. - Previously, `tidyverse` functions came in two variants: the regular, and an "underscored" one, where the latter used SE - This is no longer the case (the underscored functions were deprecated when the ideas of `rlang` were put into place) - Instead, we use the `rlang` functions I've covered with the regular functions Instead of having special functions that take strings instead of expressions, we provide the arguments as expressions using quasiquotation. For me, personally, I often saw this during my time as a student assistant at NEAS; - Many of my tasks involved creating interactive analytics dashboards with Shiny - These would in some way did some data analysis based on user inputs - Back then, however, all the things I've been talking about were developing ideas, and I had enough on my plate just learning how to wrangle data, so I never got to _actually_ use these techniques - But it serves as a prime example of a situation in which it makes sense Let's take a look at some scenarios you might find yourself in when programming with `tidyverse`. --- ### Scenario: interactive end user function .pull-left[ ```r my_count <- function(data, group_var) { data %>% group_by(group_var) %>% tally() } my_count(iris, Species) ``` ``` ## Error in [truncated]: ## Column `group_var` is unknown ``` ] .pull-right[ ```r my_count2 <- function(data, group_var) { group_var <- enexpr(group_var) data %>% group_by(!!group_var) %>% tally() } my_count2(iris, Species) ``` ``` ## # A tibble: 3 x 2 ## Species n ## <fct> <int> ## 1 setosa 50 ## 2 versicolor 50 ## 3 virginica 50 ``` ] ??? ## Scenario You want to write a function that mimics the style of `tidyverse` functions, i.e. it lets the user type in `group_var` as-is. Supplying `group_var` as-is to `group_by` makes it look for a column literally named `group_var`. ## Solution Quote `group_var` and unquote it in the context where it's used --- ### Scenario: variable selection in e.g. a Shiny app .pull-left[ ```r my_select1 <- function(data, vars) { data %>% select(vars) } df <- tibble(x = 1:5, y = 6:10, vars = letters[1:5]) my_vars <- c("x", "y") my_select1(df, my_vars) ``` ``` ## # A tibble: 5 x 1 ## vars ## <chr> ## 1 a ## 2 b ## 3 c ## 4 d ## 5 e ``` ] .pull-right[ ```r my_select2 <- function(data, vars) { vars <- ensyms(vars) data %>% select(!!!vars) } my_select2(df, my_vars) ``` ``` ## # A tibble: 5 x 2 ## x y ## <int> <int> ## 1 1 6 ## 2 2 7 ## 3 3 8 ## 4 4 9 ## 5 5 10 ``` ] ??? ## Scenario You want to let your users use `dplyr::select` in a Shiny app, where they can pick variables from e.g. a `selectInput`. While `select` _can_ be used with string vectors, you run into trouble if a column in the data has the same name as the vector (contrived, I know). ## Solution Turn the vector of strings into a list of symbols with `ensyms` and unquote-splice inside `select`. --- ### Scenario: end user function involving variable assignment .pull-left[ ```r my_adder1 <- function(data, col1, col2, newcol) { col1 <- ensym(col1) col2 <- ensym(col2) newcol <- ensym(newcol) data %>% mutate(!!newcol = !!col1 + !!col2) } my_adder1(df, x, y, z) ``` ``` ## Error: <text>:6:21: unexpected '=' ## 5: data %>% ## 6: mutate(!!newcol = ## ^ ``` - .rb[`:=`] : (colon-equals) definition operator that supports unquoting on both the LHS and RHS ] .pull-right[ ```r my_adder2 <- function(data, col1, col2, newcol) { col1 <- ensym(col1) col2 <- ensym(col2) newcol <- ensym(newcol) data %>% mutate(!!newcol := !!col1 + !!col2) } my_adder2(df, x, y, z) ``` ``` ## # A tibble: 5 x 4 ## x y vars z ## <int> <int> <chr> <int> ## 1 1 6 a 7 ## 2 2 7 b 9 ## 3 3 8 c 11 ## 4 4 9 d 13 ## 5 5 10 e 15 ``` ] ??? ## Scenario You want to make an interactive function, `tidyverse` style, that lets the user select two columns to add together to a new column, and set a name for it. However, the code `newcol = !!col1 + col2` results in a new column that's always named `newcol`, and unquoting it is invalid syntax. ## Solution Use the special colon-equals operator, `:=` --- class: center, middle ## Domain specific languages ??? Another application is DSL: language specialised to a particular application domain Examples hereof: - `dplyr` verbs translate directly into SQL code; you can use SQL by only knowing R and `dplyr` - The AST figures in this very presentation! I wrote a function that takes an expression as its argument, quotes it, and by traversing and inspecting the AST translates it into TiKZ code, which is then rendered in a template --- ## 📦 dev overview Action | `base` | `rlang` --- | --- | --- Quote input as-is | `quote` | `expr` Quote multiple inputs as-is | | `exprs` Quote user input | `substitute` / `bquote` | `enexpr` Quote multiple user inputs | | `enexprs` Turn string into symbol | `as.name` | `sym` / `ensym` Turn multiple strings into symbols | | `syms` / `ensyms` Unquote quoted expression(s) | `?bquote` | `!!` / `!!!` Evaluate expression | `eval` | `eval_bare` Evaluate quosure | | `eval_tidy` Deparse expression | `deparse` | `expr_deparse` Deparse quosure | | `quo_text` / `quo_label` / `quo_name` ??? As a closing remark on this part, here is a quick overview of some commonly used `rlang` functions and their `base` counterparts. Note the `?` on `?bquote` under unquotation. By that I mean that you should check the help file for `bquote` to see how you can get something close to unquotation in `base`. --- layout: false name: links ## Links 📕 [Advanced R](https://adv-r.hadley.nz) by Hadley Wickham (online book) - in particular, the chapter on [Metaprogramming](https://adv-r.hadley.nz/meta.html)  [Metaprogramming in Julia](https://docs.julialang.org/en/v1/manual/metaprogramming/) ― even if you don't care about Julia itself <i class="fa fa-github"></i> [repository](https://github.com/AalborgRUG/talk-nse) for this presentation with source code <i class="fa fa-github"></i> [AalborgRUG](https://github.com/AalborgRUG), a GitHub Organisation for this R User Group meant for sharing slides, notes, and code - contact [Ege](mailto:rubak@math.aau.dk) to request membership 📦 [xaringan](https://github.com/yihui/xaringan), used to make these slides (`remark.js` wrapper for R) 📦 [jsvm](https://github.com/janusvm/jsvm), used for producing the AST figures (developed by yours truly 😏) .footnote[ You can view these slides (with clickable links) online on [**`aalborgrug.github.io/talk-nse`**](https://aalborgrug.github.io/talk-nse) ] --- layout: false class: inverse, middle, center # ✨ Thank you for your time ✨