R语言rlang包中expr与exprs返回类型差异及cement1修复
关于rlang中expr/exprs差异及cement1函数报错的解决方案
1. expr和exprs的差异是设计使然吗?
没错,这完全是设计上的有意区分:
expr()的作用是捕获单个未求值的表达式,返回值的类型取决于捕获的内容:如果是变量名,就返回name类型;如果是函数调用,就返回call类型,以此类推。它适合处理单个表达式的场景。exprs()的作用是捕获多个未求值的表达式,统一返回list类型,列表中的每个元素对应一个捕获的表达式。这个设计是为了方便批量处理多个参数(比如函数的...参数),让开发者可以用列表操作工具(比如purrr::map)来处理每个表达式。
你观察到的class(expr(x))返回name、class(exprs(x))返回list,完全符合它们的设计定位,不存在操作错误。
2. cement1的报错原因及正确写法
报错原因
你原来的cement1报错,核心问题出在两个点:
- 使用
expr(x)并没有捕获用户传入的实际参数表达式,而是捕获了函数内部的x这个符号本身; - 当你调用
cement1(Hello)时,x被绑定到Hello这个未定义的符号,执行expr_name(x)时,R会尝试求值x(也就是找Hello这个对象),但它不存在,所以抛出错误。
而cement2能正常运行,是因为exprs(y,z)捕获的是y和z这两个符号本身(不管传入的实际参数是什么),expr_name(y)直接取符号的名字字符串"y",不需要求值实际参数,所以不会报错。
正确写法
根据你的需求,有两种常见的正确写法:
写法一:捕获用户传入的参数名称(返回"Hello")
如果想让函数返回你传入的参数符号的名字,需要用enexpr()来捕获用户传入的表达式,修改后的代码如下:
library(rlang) cement1 <- function(x) { dot <- enexpr(x) print(class(dot)) paste(expr_name(dot)) }
测试运行:
cement1(Hello) # 输出: # [1] "name" # [1] "Hello"
这里enexpr(x)的作用是捕获用户传入的未求值的表达式(也就是Hello这个符号),expr_name(dot)直接提取这个符号的名字字符串,不需要求值Hello,所以不会报错。
写法二:固定返回函数参数名(返回"x")
如果想让函数和cement2一样,返回函数定义里的参数名(比如不管传入什么都返回"x"),可以直接用expr_name(expr(x)),因为expr(x)捕获的是函数内部的x符号,expr_name处理的是这个存在的符号本身:
cement1 <- function(x) { dot <- expr(x) print(class(dot)) paste(expr_name(dot)) } cement1(Hello) # 输出: # [1] "name" # [1] "x"
这种写法也不会报错,因为不需要求值外部未定义的Hello。
内容的提问来源于stack exchange,提问作者PKumar
相关产品推荐
相关产品推荐

