使用rlang::inject()结合数据掩码时如何保留调用上下文以简化准引用表达式求值?
我最近在写R代码时遇到了一个关于整洁求值的问题:我想实现一个简单函数,输入是data.frame,要在这个数据的上下文里求值一个包含准引用列的表达式。
目前我用rlang包里的expr()和eval_tidy()已经能实现需求了,代码是这样的:
library(rlang) fn_eval <- function(x, col) { eval_tidy(data = x, expr( # 这里是涉及目标列的表达式 max({{ col }}) )) }
这个写法能正确对传入的列参数解引用,然后求值表达式。测试一下:
df <- data.frame(A = 1:5) fn_eval(df, A) #> [1] 5
结果没问题,但我总觉得eval()的嵌套写法有点冗余,想简化一下。于是我试着直接用inject(),把as_data_mask(x)作为环境参数传进去,代码改成了这样:
fn_inj <- function(x, col) { inject(env = as_data_mask(x), # 同样是涉及目标列的表达式 max({{ col }}) ) }
但运行的时候出问题了——max()函数直接找不到,报了这个错:
fn_inj(df, A) #> Error in max(~A) : could not find function "max"
更麻烦的是,就算我写成base::max()也没用,因为::运算符也不在这个数据掩码的作用域里——说白了,数据掩码外面的所有东西都访问不到。显然inject()这里的行为和eval_tidy()创建的数据掩码完全不一样。
现在我有两个具体的问题想请教:
我的疑问
怎么给
inject()创建一个能继承调用上下文的数据掩码,让max()这类常用函数保持在作用域里?
我查了as_data_mask的文档,里面说不能直接修改掩码的父环境:注意:顶层环境的父环境是完全不确定的,你不能指望它一直保持不变。在求值过程中,
eval_tidy()会把这个父环境替换成以下环境之一...但我真的不想手动把需要的函数一个个绑定到掩码里,因为这样每次表达式依赖的函数变化时,我都得手动更新绑定,太繁琐了。文档里还提到过:
在某些情况下,你的数据掩码需要多个层级。一个很好的理由是当你在掩码中包含函数时。最好把数据对象放在比函数对象低的层级,这样前者就不会覆盖后者的定义(见示例)。
有没有标准的、推荐的写法,可以在
x的上下文中求值准引用表达式,同时让x的列能覆盖调用环境?
理想情况是这个写法用到的数据掩码完全支持整洁求值的所有特性:比如准引用、.data代词这些都能正常工作。
备注:内容来源于stack exchange,提问作者Greg

