Zek*_*eke 8 evaluation r infix-operator iterable-unpacking
我试图在 R(例如,a, *b, c = [1,2,3], "C")中使 Python 3 样式的赋值解包成为可能,尽管我非常接近(您可以在此处查看我的代码),但我最终还是遇到了一些(奇怪的)问题。
我的代码是这样工作的:
a %,*% b %,% c <- c(1,2,3,4,5)
Run Code Online (Sandbox Code Playgroud)
并将分配a= 1、b=c(2,3,4)和c= 5(我的代码实际上确实做到了这一点,但有一个小问题我稍后会讲到)。
为了让它做任何事情,我必须定义:
`%,%` <- function(lhs, rhs) {
...
}
Run Code Online (Sandbox Code Playgroud)
和
`%,%<-` <- function(lhs, rhs, value) {
...
}
Run Code Online (Sandbox Code Playgroud)
(以及%,*%和%,*%<-,它们是先前函数的轻微变体)。
*tmp*的lhs参数据我所知,R 首先从左到右评估这段代码(即,从a到c,直到它到达最后一个%,%,然后,它从右到左返回,沿途分配值。但是第一个我注意到的奇怪的事情是,当我做match.call()或substitute(lhs)在类似的事情中x %infix% y <- z,它说输入到lhs参数中的%infix%是*tmp*,而不是说,a或x。
这对我来说很奇怪,我在 R 手册或文档中找不到任何提及。我实际上在我的代码中使用了这个奇怪的约定(即,它没有在赋值的右侧显示这种行为,所以我可以使用*tmp*输入的存在来使%,%赋值的这一侧表现不同),但我不知道为什么会这样。
我的第二个问题是什么使我的代码最终不起作用。我注意到,如果您在任何赋值的左侧以变量名称开头,R 似乎甚至不会开始计算表达式——它返回错误object '<variable name>' not found。即,如果x未定义,x %infix% y <- z即使%infix%不实际使用或评估也不会评估x。
为什么 R 会这样,我可以改变它或绕过它吗? 如果我可以%,% 在R 检查之前运行代码以查看是否x存在,我可能可以破解它以便我不会成为问题,并且我的 Python 解包代码将足够有用以实际共享。但就像现在一样,第一个变量需要已经存在,在我看来这太局限了。我知道我可能可以通过将 更改<-为自定义中缀运算符来做一些事情,例如%<-%,但是我的代码与zeallot包非常相似,我认为不值得。(它的功能已经非常接近了,但我更喜欢我的风格。)
按照 Ben Bolker 的出色建议,我找到了解决问题的方法……通过覆盖<-.
`<-` <- function(x, value) {
base::`<-`(`=`, base::`=`)
find_and_assign(match.call(), parent.frame())
do.call(base::`<-`, list(x = substitute(x), value = substitute(value)),
quote = FALSE, envir = parent.frame())
}
find_and_assign <- function(expr, envir) {
base::`<-`(`<-`, base::`<-`)
base::`<-`(`=`, base::`=`)
while (is.call(expr)) expr <- expr[[2]]
if (!rlang::is_symbol(expr)) return()
var <- rlang::as_string(expr) # A little safer than `as.character()`
if (!exists(var, envir = envir)) {
assign(var, NULL, envir = envir)
}
}
Run Code Online (Sandbox Code Playgroud)
我很确定这将是一个致命的罪过,对吧?我无法确切地看到它会如何搞砸任何事情,但是我的程序员感觉的刺痛告诉我这不适合共享诸如包之类的东西......这会有多糟糕?
对于您的第一个问题,关于*tmp*(可能与您的第二个问题有关):
对结构子集的赋值是复杂赋值通用机制的一个特例:
x[3:5] <- 13:15
Run Code Online (Sandbox Code Playgroud)
这个命令的结果就好像已经执行了以下内容
`*tmp*` <- x
x <- "[<-"(`*tmp*`, 3:5, value=13:15)
rm(`*tmp*`)
Run Code Online (Sandbox Code Playgroud)
请注意,索引首先转换为数字索引,然后元素沿着数字索引顺序替换,就像使用了
for循环一样。任何被调用的现有变量*tmp*都将被覆盖和删除,并且不应在代码中使用该变量名。
相同的机制可以应用于除
[. 替换函数与<-粘贴同名。它必须调用的最后一个参数value是要分配的新值。
我可以想象,您的第二个问题与“好像”代码的第一步有关:如果 R 在内部尝试评估,则此时可能无法阻止尝试评估*tmp* <- xx...
如果你想进一步挖掘,我认为用于处理“复杂赋值”的内部评估代码(因为它似乎在内部注释中被称为)就在这里......