metaprogramming

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

R Metaprogramming with rlang

基于rlang的R元编程

Metaprogramming is the ability to defuse, create, and inject R expressions. The core pattern is defuse-and-inject: capture code as data, optionally transform it, then inject it into another context for evaluation.
元编程指的是对R表达式进行延迟求值、创建和注入的能力。核心模式是延迟求值-注入:将代码捕获为数据,可选择对其进行转换,然后将其注入到另一个上下文中执行。

Quick Reference

速查参考

TaskFunction/Operator
Defuse your own expression
expr(x + 1)
Defuse user's single argument
enquo(arg)
Defuse user's
...
arguments
enquos(...)
Inject single expression
!!
or
{{
Splice list of expressions
!!!
Get expression from quosure
quo_get_expr(q)
Get environment from quosure
quo_get_env(q)
Build symbol from string
sym("name")
Build symbol with .data pronoun
data_sym("name")
Build symbols from vector
syms(names)
/
data_syms(names)
Auto-label expression
as_label(quo)
Format argument as string
englue("{{ x }}")
Interpolate name in dynamic dots
"{name}" := value
Interpolate argument in name
"{{ arg }}" := value
任务函数/操作符
延迟求值自定义表达式
expr(x + 1)
延迟求值用户的单个参数
enquo(arg)
延迟求值用户的
...
参数
enquos(...)
注入单个表达式
!!
{{
拼接注入表达式列表
!!!
从quosure中提取表达式
quo_get_expr(q)
从quosure中提取环境
quo_get_env(q)
从字符串构建符号
sym("name")
使用.data代词构建符号
data_sym("name")
从向量构建符号
syms(names)
/
data_syms(names)
自动为表达式添加标签
as_label(quo)
将参数格式化为字符串
englue("{{ x }}")
在动态点中插值名称
"{name}" := value
在名称中插值参数
"{{ arg }}" := value

Defusing Expressions

延迟求值表达式

Defusing stops evaluation and returns the expression as a tree-like object (a "blueprint" for computation).
r
undefined
延迟求值会停止表达式的执行,将其作为树形对象返回(即计算的“蓝图”)。
r
undefined

Normal evaluation returns result

常规执行返回结果

1 + 1 #> [1] 2
1 + 1 #> [1] 2

Defusing returns the expression

延迟求值返回表达式

expr(1 + 1) #> 1 + 1
undefined
expr(1 + 1) #> 1 + 1
undefined

expr() vs enquo()

expr() vs enquo()

FunctionDefusesReturnsUse When
expr()
Your own codeExpressionBuilding expressions locally
enquo()
User's argumentQuosureForwarding function arguments
enquos()
User's
...
List of quosuresForwarding multiple arguments
r
undefined
函数延迟求值对象返回值使用场景
expr()
自定义代码表达式本地构建表达式
enquo()
用户的参数Quosure转发函数参数
enquos()
用户的
...
参数
Quosure列表转发多个参数
r
undefined

Defuse your own expression

延迟求值自定义表达式

my_expr <- expr(mean(x, na.rm = TRUE))
my_expr <- expr(mean(x, na.rm = TRUE))

Defuse user's function argument (returns quosure)

延迟求值用户的函数参数(返回quosure)

my_function <- function(var) {
enquo(var) } my_function(cyl + am) #> <quosure> #> expr: ^cyl + am #> env: global
undefined
my_function <- function(var) {
enquo(var) } my_function(cyl + am) #> <quosure> #> expr: ^cyl + am #> env: global
undefined

enquos() with .named

带.named参数的enquos()

Auto-label unnamed arguments:
r
g <- function(...) {

  vars <- enquos(..., .named = TRUE)
  names(vars)
}
g(cyl, 1 + 1)
#> [1] "cyl"   "1 + 1"

g(foo = cyl, bar = 1 + 1)
#> [1] "foo" "bar"
自动为未命名参数添加标签:
r
g <- function(...) {

  vars <- enquos(..., .named = TRUE)
  names(vars)
}
g(cyl, 1 + 1)
#> [1] "cyl"   "1 + 1"

g(foo = cyl, bar = 1 + 1)
#> [1] "foo" "bar"

Types of Defused Expressions

延迟求值表达式的类型

  • Calls:
    f(x, y)
    ,
    1 + 1
    - function invocations
  • Symbols:
    x
    ,
    df
    - named object references
  • Constants:
    1
    ,
    "text"
    ,
    NULL
    - literal values
  • 调用(Calls)
    f(x, y)
    1 + 1
    - 函数调用
  • 符号(Symbols)
    x
    df
    - 命名对象引用
  • 常量(Constants)
    1
    "text"
    NULL
    - 字面量值

Quosures

Quosure

A quosure wraps an expression with its original environment. This is critical for correct evaluation when expressions travel across function and package boundaries.
Quosure是将表达式与其原始环境封装在一起的对象。当表达式跨函数或包边界传递时,这对于确保正确执行至关重要。

Why Environments Matter

环境的重要性

r
undefined
r
undefined

In package A

在包A中

my_function <- function(data, var) {

'var' was defined in the user's environment

The quosure tracks that environment

var <- enquo(var)

When passed to package B's function, the quosure

ensures symbols resolve in the correct environment

pkg_b_function(data, !!var) }

Without environment tracking, symbols might resolve to wrong objects when code crosses package boundaries.
my_function <- function(data, var) {

'var'是在用户环境中定义的

Quosure会追踪该环境

var <- enquo(var)

当传递给包B的函数时,Quosure

确保符号在正确的环境中解析

pkg_b_function(data, !!var) }

如果没有环境追踪,当代码跨包边界时,符号可能会解析到错误的对象。

When You Need Quosures

何时需要Quosure

SituationUse Quosure?
Defusing function argumentsYes - use
enquo()
Building local expressionsNo - use
expr()
Cross-package compositionYes - environments matter
Simple local evaluationNo -
expr()
+
eval()
suffices
场景是否需要Quosure?
延迟求值函数参数是 - 使用
enquo()
构建本地表达式否 - 使用
expr()
跨包组合代码是 - 环境很重要
简单本地执行否 -
expr()
+
eval()
即可

Quosure Operations

Quosure操作

r
q <- enquo(x + 1)

quo_get_expr(q)   # Extract expression: x + 1
quo_get_env(q)    # Extract environment
r
q <- enquo(x + 1)

quo_get_expr(q)   # 提取表达式: x + 1
quo_get_env(q)    # 提取环境

Create quosure manually

手动创建Quosure

new_quosure(expr(x + 1), env = global_env())
new_quosure(expr(x + 1), env = global_env())

Convert expression to quosure

将表达式转换为Quosure

as_quosure(expr(x + 1), env = global_env())
undefined
as_quosure(expr(x + 1), env = global_env())
undefined

Injection Operators

注入操作符

Injection inserts defused expressions back into code before evaluation.
注入指的是在执行前将延迟求值的表达式重新插入到代码中。

{{
(Embrace)

{{
(Embrace)

Defuses and injects in one step. Equivalent to
!!enquo(arg)
:
r
undefined
一步完成延迟求值和注入。等效于
!!enquo(arg)
r
undefined

These are equivalent:

以下两种写法等效:

my_summarise <- function(data, var) {
data |> dplyr::summarise({{ var }}) }
my_summarise <- function(data, var) { data |> dplyr::summarise(!!enquo(var)) }

Use `{{` when you simply need to forward an argument. Use `enquo()` + `!!` when you need to inspect or transform the expression first.
my_summarise <- function(data, var) {
data |> dplyr::summarise({{ var }}) }
my_summarise <- function(data, var) { data |> dplyr::summarise(!!enquo(var)) }

当仅需转发参数时使用`{{`。当需要先检查或转换表达式时,使用`enquo()` + `!!`。

!!
(Bang-Bang)

!!
(Bang-Bang)

Injects a single expression:
r
var <- expr(cyl)
mtcars |> dplyr::summarise(mean(!!var))
#> Equivalent to: summarise(mean(cyl))
注入单个表达式:
r
var <- expr(cyl)
mtcars |> dplyr::summarise(mean(!!var))
#> 等效于: summarise(mean(cyl))

Inject a value to avoid name collisions

注入值以避免名称冲突

x <- 100 df |> dplyr::mutate(x = x / !!x) #> Uses column x divided by env value 100
undefined
x <- 100 df |> dplyr::mutate(x = x / !!x) #> 使用列x除以环境中的值100
undefined

!!!
(Splice)

!!!
(Splice)

Injects each element of a list as separate arguments:
r
vars <- exprs(cyl, am, vs)
mtcars |> dplyr::select(!!!vars)
#> Equivalent to: select(cyl, am, vs)
将列表中的每个元素作为单独参数注入:
r
vars <- exprs(cyl, am, vs)
mtcars |> dplyr::select(!!!vars)
#> 等效于: select(cyl, am, vs)

With enquos()

结合enquos()使用

my_group_by <- function(.data, ...) { .data |> dplyr::group_by(!!!enquos(...)) }
undefined
my_group_by <- function(.data, ...) { .data |> dplyr::group_by(!!!enquos(...)) }
undefined

Where Operators Work

操作符的适用场景

  • Data-masked arguments: Implicitly enabled (dplyr, ggplot2, etc.)
  • inject(): Explicitly enables operators in any context
  • Dynamic dots:
    !!!
    and
    {name}
    work in functions using
    list2()
r
undefined
  • 数据掩码参数:默认支持(如dplyr、ggplot2等)
  • inject():在任意上下文中显式启用操作符
  • 动态点
    !!!
    {name}
    在使用
    list2()
    的函数中生效
r
undefined

Enable injection in base functions

在基础R函数中启用注入

inject( with(mtcars, mean(!!sym("cyl"))) )
undefined
inject( with(mtcars, mean(!!sym("cyl"))) )
undefined

Building Expressions from Data

从数据构建表达式

sym() and syms()

sym() 和 syms()

Convert strings to symbols:
r
var <- "cyl"
sym(var)
#> cyl

vars <- c("cyl", "am")
syms(vars)
#> [[1]]
#> cyl
#> [[2]]
#> am
将字符串转换为符号:
r
var <- "cyl"
sym(var)
#> cyl

vars <- c("cyl", "am")
syms(vars)
#> [[1]]
#> cyl
#> [[2]]
#> am

data_sym() and data_syms()

data_sym() 和 data_syms()

Create
.data$col
expressions (safer in tidy eval, avoids collisions):
r
data_sym("cyl")
#> .data$cyl

data_syms(c("cyl", "am"))
#> [[1]]
#> .data$cyl
#> [[2]]
#> .data$am
Use
sym()
for base R functions; use
data_sym()
for tidy eval functions.
创建
.data$col
表达式(在tidy eval中更安全,避免冲突):
r
data_sym("cyl")
#> .data$cyl

data_syms(c("cyl", "am"))
#> [[1]]
#> .data$cyl
#> [[2]]
#> .data$am
在基础R函数中使用
sym()
;在tidy eval函数中使用
data_sym()

Building Calls

构建调用表达式

r
undefined
r
undefined

With call()

使用call()

call("mean", sym("x"), na.rm = TRUE) #> mean(x, na.rm = TRUE)
call("mean", sym("x"), na.rm = TRUE) #> mean(x, na.rm = TRUE)

With expr() and injection

使用expr()和注入

var <- sym("x") expr(mean(!!var, na.rm = TRUE)) #> mean(x, na.rm = TRUE)
undefined
var <- sym("x") expr(mean(!!var, na.rm = TRUE)) #> mean(x, na.rm = TRUE)
undefined

Name Interpolation (Glue Operators)

名称插值(Glue操作符)

In dynamic dots, use glue syntax for names.
在动态点中,使用glue语法处理名称。

{
for Variable Values

{}
用于变量值

r
name <- "foo"
tibble::tibble("{name}" := 1:3)
#> # A tibble: 3 x 1
#>     foo
#>   <int>
#> 1     1
#> 2     2
#> 3     3

tibble::tibble("prefix_{name}" := 1:3)
#> Column named: prefix_foo
r
name <- "foo"
tibble::tibble("{name}" := 1:3)
#> # A tibble: 3 x 1
#>     foo
#>   <int>
#> 1     1
#> 2     2
#> 3     3

tibble::tibble("prefix_{name}" := 1:3)
#> 列名为: prefix_foo

{{
for Argument Labels

{{}}
用于参数标签

r
my_mutate <- function(data, var) {
  data |> dplyr::mutate("mean_{{ var }}" := mean({{ var }}))
}
mtcars |> my_mutate(cyl)
#> Creates column: mean_cyl
r
my_mutate <- function(data, var) {
  data |> dplyr::mutate("mean_{{ var }}" := mean({{ var }}))
}
mtcars |> my_mutate(cyl)
#> 创建列: mean_cyl

englue() for String Formatting

englue() 用于字符串格式化

r
my_function <- function(var) {
  englue("Column: {{ var }}")
}
my_function(some_column)
#> [1] "Column: some_column"
r
my_function <- function(var) {
  englue("Column: {{ var }}")
}
my_function(some_column)
#> [1] "Column: some_column"

Advanced: Manual Expression Transformation

进阶:手动转换表达式

When you need to modify expressions before injection:
r
my_mean <- function(data, var) {
  # 1. Defuse

  var <- enquo(var)

  # 2. Transform: wrap in mean()
  wrapped <- expr(mean(!!var, na.rm = TRUE))

  # 3. Inject
  data |> dplyr::summarise(mean = !!wrapped)
}
For multiple arguments:
r
my_mean <- function(.data, ...) {
  vars <- enquos(..., .named = TRUE)

  # Transform each expression
  vars <- purrr::map(vars, ~ expr(mean(!!.x, na.rm = TRUE)))

  .data |> dplyr::summarise(!!!vars)
}
当需要在注入前修改表达式时:
r
my_mean <- function(data, var) {
  # 1. 延迟求值

  var <- enquo(var)

  # 2. 转换:用mean()包裹
  wrapped <- expr(mean(!!var, na.rm = TRUE))

  # 3. 注入
  data |> dplyr::summarise(mean = !!wrapped)
}
处理多个参数的情况:
r
my_mean <- function(.data, ...) {
  vars <- enquos(..., .named = TRUE)

  # 转换每个表达式
  vars <- purrr::map(vars, ~ expr(mean(!!.x, na.rm = TRUE)))

  .data |> dplyr::summarise(!!!vars)
}

Base R Equivalents

基础R等效实现

rlangBase RNotes
expr()
bquote()
bquote uses
.()
for injection
enquo()
substitute()
substitute returns naked expr, not quosure
enquos(...)
eval(substitute(alist(...)))
Workaround for dots
!!
.()
in bquote
Only inside bquote
eval_tidy()
eval()
eval_tidy supports .data/.env pronouns
rlang基础R说明
expr()
bquote()
bquote使用
.()
进行注入
enquo()
substitute()
substitute返回裸表达式,而非Quosure
enquos(...)
eval(substitute(alist(...)))
处理可变参数的变通方法
!!
bquote中的
.()
仅在bquote内生效
eval_tidy()
eval()
eval_tidy支持.data/.env代词

Pitfalls

常见陷阱

{{
on Non-Arguments

对非参数使用`{{}}

{{
should only wrap function arguments. On regular objects, it captures the value, not the expression:
r
undefined
{{}}
仅应包裹函数参数。对常规对象使用时,它会捕获值而非表达式:
r
undefined

Correct: var is a function argument

正确用法:var是函数参数

my_fn <- function(var) {{ var }}
my_fn <- function(var) {{ var }}

Problematic: x is not an argument

错误用法:x不是参数

x <- 1 {{ x }} # Returns 1, not the expression
undefined
x <- 1 {{ x }} # 返回1,而非表达式
undefined

Operators Out of Context

操作符在非目标上下文的问题

Outside tidy eval/inject contexts, operators have different meanings:
OperatorIntendedOutside Context
{{
EmbraceDouble braces (returns value)
!!
InjectDouble negation (logical)
!!!
SpliceTriple negation (logical)
These fail silently. See the tidy-evaluation skill for details on proper usage contexts.
在tidy eval/inject上下文之外,操作符有不同含义:
操作符预期用途非目标上下文含义
{{}}
Embrace双大括号(返回值)
!!
注入双重否定(逻辑运算)
!!!
拼接三重否定(逻辑运算)
这些情况会静默失败。有关正确使用场景的详细信息,请参阅tidy-evaluation技能文档。

See Also

另请参阅

  • tidy-evaluation: Programming patterns for data-masked functions
  • designing-tidy-r-functions: Function API design principles
  • rlang-conditions: Error handling with rlang
  • tidy-evaluation:数据掩码函数的编程模式
  • designing-tidy-r-functions:函数API设计原则
  • rlang-conditions:使用rlang进行错误处理

Reference Files

参考文件

  • topic-quosure.md - Complete quosure reference
  • topic-metaprogramming.md - Advanced transformation patterns
  • topic-multiple-columns.md - Multiple columns patterns
  • topic-quosure.md - 完整的Quosure参考文档
  • topic-metaprogramming.md - 进阶转换模式
  • topic-multiple-columns.md - 多列处理模式

Vignettes

小手册

Access detailed rlang documentation via R:
r
undefined
通过R访问rlang的详细文档:
r
undefined

Defusing expressions

延迟求值表达式

vignette("topic-defuse", package = "rlang")
vignette("topic-defuse", package = "rlang")

Injection operators

注入操作符

vignette("topic-inject", package = "rlang")
vignette("topic-inject", package = "rlang")

Or browse all vignettes

或浏览所有小手册

browseVignettes("rlang")
undefined
browseVignettes("rlang")
undefined