rlang包{{curly-curly}}与!!bang-bang运算符底层实现机制问询
rlang 中
{{、!! 运算符的底层实现逻辑 首先要明确:R 原生语法中不存在 {{(curly-curly)和 !!(bang-bang)这类特殊运算符,二者完全是 rlang 基于 R 原生非标准求值能力封装的语法糖,仅在 rlang 生态的非标准求值上下文(也就是基于 rlang 开发的函数内部)生效,直接在普通 R 代码中使用只会被识别为普通的大括号、取反运算符,不会产生预期效果。
核心实现步骤
所有逻辑都围绕 抽象语法树(AST)重写 展开,分为四个核心阶段:
- 第一步:捕获未求值的表达式
基于 rlang 开发的非标准求值函数,不会直接对用户传入的参数求值,而是先通过 R 原生的表达式捕获能力(底层对应substitute类的接口,rlang 封装为enquo、enexpr等工具),把用户传入的参数完整转换成 AST 结构保留,同时绑定参数对应的环境信息。 - 第二步:扫描 AST 匹配特殊运算符模式
rlang 会用 C 代码遍历整个捕获到的 AST,匹配两种特定结构:连续两个!组成的!!节点,以及双层大括号{{}}包裹的节点。这也是你在 R 源码层面找不到这两个运算符定义的原因——它们根本不是 R 层面注册的运算符,只是 AST 中特定的节点组合,只有 rlang 的 C 扫描逻辑会识别。 - 第三步:重写 AST 完成替换
对匹配到的节点做对应处理:- 遇到
{{ foo }}时,内部等价于执行rlang::enquo(foo)捕获foo对应的表达式和环境,再把 AST 中{{ foo }}的位置直接替换为捕获到的 quosure 内容 - 遇到
!! bar时,直接对bar求值,再把求值结果替换到 AST 中!! bar的位置
- 遇到
- 第四步:在目标上下文求值重写后的 AST
完成所有重写后,rlang 会把调整后的 AST 放到指定的求值上下文(比如数据框数据掩码,优先查找数据框列名,再回溯到参数绑定的环境)中执行,得到最终结果。
补充说明
你看到的 {{ foo }} 等价于 !!rlang::enquo(foo) 的说法完全准确,curly-curly 本质就是 bang-bang + 表达式捕获的组合语法糖,只是帮你省略了手动写 enquo 和 !! 的步骤,降低非标准求值代码的书写成本。
这部分逻辑用 C 实现的主要原因是 AST 遍历和重写的性能需求:如果用 R 代码处理复杂嵌套的表达式,执行效率会非常低,用 C 实现能大幅降低非标准求值的性能开销,适配 tidymodels、dplyr 等高频调用场景的需求。
内容的提问来源于stack exchange,提问作者wurli
相关产品推荐
相关产品推荐

