Metaprogramming

Many R functions evaluate arguments in a special context:

33.1 Code as data

e <- quote(x + y * 2)        # an unevaluated call
class(e); typeof(e)          # "call", "language"
e[[1]]; as.list(e)           # `+`, x, y * 2
eval(e, list(x = 1, y = 3))  # 7
expr <- parse(text = "1 + 2")[[1]]; eval(expr)
deparse(e)                   # "x + y * 2"
call("round", 10.5)          # round(10.5)
as.call(list(as.name("sum"), 1, 2)) |> eval()
f <- function(x) substitute(x); f(a + b)    # captures the expression: a + b
bquote(.(x) + y, list(x = 5))               # quasiquotation in base R
body(f) <- quote(x * 100)                    # modify a function's code

33.2 Non-standard evaluation (NSE)

Many R functions evaluate arguments in a special context:

subset(mtcars, mpg > 25, select = c(mpg, wt))   # mpg refers to a column
with(mtcars, mean(mpg))
library(ggplot2); ggplot(mtcars, aes(wt, mpg))   # wt, mpg are column names
deparse(substitute(x))                           # get the argument's expression as text (e.g. for labels)

33.3 Tidy evaluation (rlang)

Programming with dplyr/ggplot2 requires passing column names through functions:

library(dplyr); library(rlang)

# Embracing with {{ }} — most common
group_mean <- function(df, group, var) {
  df |> group_by({{ group }}) |> summarise(mean = mean({{ var }}, na.rm = TRUE))
}
group_mean(penguins, species, body_mass_g)

# Column names as strings: .data pronoun
group_mean_str <- function(df, group, var) {
  df |> group_by(.data[[group]]) |> summarise(mean = mean(.data[[var]], na.rm = TRUE))
}
group_mean_str(penguins, "species", "body_mass_g")

# Dynamic output names with glue syntax and :=
add_double <- function(df, var) mutate(df, "{{ var }}_x2" := {{ var }} * 2)

# Passing ... through
my_summary <- function(df, ...) df |> group_by(...) |> summarise(n = n())

# Lower-level: enquo / !! / !!! / sym / syms
f <- function(df, var) { v <- enquo(var); df |> summarise(m = mean(!!v)) }
cols <- syms(c("bill_length_mm", "bill_depth_mm")); penguins |> select(!!!cols)
expr(mean(x)); exprs(a + b, c * d); quo(x + 1); eval_tidy(quo(x + 1), list(x = 2))

33.4 Building code programmatically

make_filter <- function(var, value) expr(!!sym(var) > !!value)
cond <- make_filter("mpg", 25)
filter(mtcars, !!cond)

f <- new_function(alist(x = , y = 2), quote(x + y))   # rlang::new_function
f(1)   # 3

33.5 Use cases & cautions

Domain-specific languages (formulas, dplyr, data.table), automatic labels, code generation, debugging tools. Avoid eval(parse(text = ...)) on untrusted input; prefer expressions and symbols over string pasting.