我试图在R语言中实现Python 3风格的解包赋值(例如,
我的代码应该像这样工作:
并将会分配
第一个问题:为什么R在
a, *b, c = [1,2,3], "C"
),虽然我接近成功了(你可以在这里查看我的代码),但最终遇到了一些(奇怪的)问题。我的代码应该像这样工作:
a %,*% b %,% c <- c(1,2,3,4,5)
并将会分配
a
= 1
,b
= c(2,3,4)
和 c
= 5
。为了让这个工作正常,我必须定义:`%,%` <- function(lhs, rhs) {
...
}
和
`%,%<-` <- function(lhs, rhs, value) {
...
}
(同时还有%,*%
和%,*%<-
,它们是前面函数的轻微变体)。
第一个问题:为什么R在lhs
参数中将*tmp*
替换为其他值
据我了解,R首先从左到右评估此代码(即,从a
到c
),直到到达最后一个%,%
,然后从右到左返回,途中依次赋值。但我注意到的第一件奇怪的事情是,当我在类似于x %infix% y < - z
这样的语句中执行match.call()
或substitute(lhs)
时,它说%infix%
中lhs
参数的输入是*tmp*
,而不是a
或x
等。
对我来说,这太奇怪了,我在R手册或文档中找不到任何提及它的信息。实际上我在我的代码中利用了这种奇怪的约定(即,在赋值语句的右侧不显示此行为,因此我可以使用*tmp*
作为变量标记,使得%,%
在赋值语句的右侧表现出不同的行为),但我不知道为什么会这样。
第二个问题:为什么R在任何其他操作之前检查对象是否存在
我的第二个问题是导致我的代码最终无法工作的原因。我注意到,如果你在任何赋值语句的左边使用一个变量名字,R似乎甚至不会开始评估表达式——它会返回错误object '<variable name>' not found
。也就是说,如果未定义x
,则x %infix% y < - z
不会被评估,即使%infix%
没有实际使用或评估x
。
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)
}
}
我非常确定这将是一种致命的罪过,对吗?我不太确定它会如何搞砸任何事情,但我的程序员直觉告诉我这不适合在像软件包这样的东西中分享... 那么这有多糟糕?
%,%<-
?) - Ben Bolkerbase
函数的规定(tidyverse已经做了很多这方面的工作,尽管没有像<-
这样基本的东西 - 另一方面,与您的代码不同,它打算以不兼容的方式改变行为(例如filter()
)。 - Ben Bolker