R语言rlang包中的!!、!!!运算符是如何定义的?
特殊运算符!!/!!!的本质
你之前接触的%>%这类包裹在%之间的是R原生支持的标准自定义中缀运算符,而rlang包中的!!、!!!本质上不是R原生认可的普通运算符,属于rlang自定义的准引用语法糖,你看到的rlang::!!``函数只是一个占位符:
这个占位符的唯一作用是避免R在语法检查阶段报“未定义运算符”的错误,正常直接调用就会触发你看到的报错,实际在准引用上下文(比如
expr()、dplyr数据处理函数)中使用时,rlang会在表达式求值前就捕获并处理AST,根本不会执行这个占位符函数的逻辑。
这类非%包裹特殊运算符的实现逻辑
核心是基于R的抽象语法树(AST)遍历操作实现,步骤如下:
- 先定义对应运算符的占位符函数,满足R的基础语法要求
- 编写自定义的表达式捕获函数,在函数执行前先捕获用户输入的未求值表达式AST
- 遍历AST的所有节点,识别到目标运算符的调用节点时,执行你预设的自定义逻辑,替换对应AST节点
- 求值处理完成后的AST,返回结果
自定义特殊运算符示例
我们以实现一个和!!功能类似的自定义解引用运算符$$为例:
第一步:定义占位符函数
`$$` <- function(x) { stop("`$$` can only be used within custom quasiquoted functions") }
第二步:编写自定义表达式处理函数
# 加载处理AST需要的rlang包,也可以用原生函数实现 library(rlang) my_eval <- function(expr) { # 捕获未求值的输入表达式 expr_ast <- substitute(expr) # 递归遍历AST处理$$节点 process_ast <- function(node) { # 识别到$$调用时,直接求值运算符后接的参数,替换原节点 if (is_call(node, "$$")) { return(eval(node[[2]], parent.frame(2))) } # 递归处理子节点 if (is.call(node) || is.pairlist(node)) { return(lapply(node, process_ast)) } return(node) } # 转换处理后的节点为可调用表达式 processed_ast <- as.call(process_ast(expr_ast)) # 求值并返回结果 eval(processed_ast, parent.frame()) }
第三步:测试效果
val <- 200 my_eval(50 + $$val) # 输出:250,$$val被提前替换为val的值200再参与计算
注意事项
- 这类特殊运算符仅在你自定义的表达式捕获函数中生效,脱离对应上下文直接调用只会触发占位符的报错
- 你可以通过修改AST处理逻辑,自定义任意格式的运算符功能,包括中缀、前缀、后缀运算符
内容的提问来源于stack exchange,提问作者passiflora
相关产品推荐
相关产品推荐

