R 如何评估这些奇怪的表达式?
How does R evaluate these weird expressions?
我试图在 R 中实现 Python 3 式赋值解包(例如,a, *b, c = [1,2,3], "C"
),虽然我 so 关闭(你可以查看我的代码 here),我最终 运行 遇到了一些(奇怪的)问题。
我的代码是这样工作的:
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 用 *tmp*
代替 lhs
参数
据我所知,R 首先从左到右评估此代码(即从 a
到 c
,直到到达最后一个 %,%
,从那以后,它从右向左返回,沿途分配值。但我注意到的第一件奇怪的事情是,当我在 x %infix% y <- z
之类的东西中执行 match.call()
或 substitute(lhs)
时,它说 %infix%
中 lhs
参数的输入是 *tmp*
,而不是 a
或 x
.
这对我来说很奇怪,我在 R 手册或文档中找不到任何提及它的地方。我实际上在我的代码中 use 这个奇怪的约定(即,它没有在赋值的右侧显示这种行为,所以我可以使用 *tmp*
输入使 %,%
在赋值的这一侧表现不同),但我不知道 为什么 它这样做。
第二个问题:为什么 R 在 anything else
之前检查对象是否存在
我的第二个问题是导致我的代码最终无法运行的原因。我注意到,如果您在任何赋值的左侧以变量名开头,R 似乎甚至不会开始计算表达式——它 returns 错误 object '<variable name>' not found
。即,如果 x
未定义,x %infix% y <- z
将不会计算,即使 %infix%
实际上并未使用或计算 x
.
为什么 R 会这样,我可以更改它或绕过它吗? 如果我可以 运行 %,%
中的代码 before 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)
}
}
我很确定这将是一个致命的罪过,对吧?我无法 确切地 看到它会如何搞砸任何事情,但我的程序员感官的刺痛告诉我这 不 适合分享像包裹之类的东西... 这会有多糟糕?
关于你的第一个问题,关于 *tmp*
(可能与你的第二个问题有关):
来自 Section 3.4.4 of the R Language definition:
Assignment to subsets of a structure is a special case of a general mechanism for complex assignment:
x[3:5] <- 13:15
The result of this command is as if the following had been executed
`*tmp*` <- x
x <- "[<-"(`*tmp*`, 3:5, value=13:15)
rm(`*tmp*`)
Note that the index is first converted to a numeric index and then the elements are replaced sequentially along the numeric index, as if a for
loop had been used. Any existing variable called *tmp*
will be overwritten and deleted, and this variable name should not be used in code.
The same mechanism can be applied to functions other than [
. The replacement function has the same name with <-
pasted on. Its last argument, which must be called value
, is the new value to be assigned.
我可以想象你的第二个问题与“好像”代码的第一步有关:如果 R 在内部尝试评估 <code>*tmp*
<- x,可能无法阻止此时尝试评估 x
...
如果你想深入挖掘,我认为用于处理“复杂赋值”(内部注释中似乎这样称呼)的内部评估代码大约在 here ...
我试图在 R 中实现 Python 3 式赋值解包(例如,a, *b, c = [1,2,3], "C"
),虽然我 so 关闭(你可以查看我的代码 here),我最终 运行 遇到了一些(奇怪的)问题。
我的代码是这样工作的:
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 用 *tmp*
代替 lhs
参数
据我所知,R 首先从左到右评估此代码(即从 a
到 c
,直到到达最后一个 %,%
,从那以后,它从右向左返回,沿途分配值。但我注意到的第一件奇怪的事情是,当我在 x %infix% y <- z
之类的东西中执行 match.call()
或 substitute(lhs)
时,它说 %infix%
中 lhs
参数的输入是 *tmp*
,而不是 a
或 x
.
这对我来说很奇怪,我在 R 手册或文档中找不到任何提及它的地方。我实际上在我的代码中 use 这个奇怪的约定(即,它没有在赋值的右侧显示这种行为,所以我可以使用 *tmp*
输入使 %,%
在赋值的这一侧表现不同),但我不知道 为什么 它这样做。
第二个问题:为什么 R 在 anything else
之前检查对象是否存在我的第二个问题是导致我的代码最终无法运行的原因。我注意到,如果您在任何赋值的左侧以变量名开头,R 似乎甚至不会开始计算表达式——它 returns 错误 object '<variable name>' not found
。即,如果 x
未定义,x %infix% y <- z
将不会计算,即使 %infix%
实际上并未使用或计算 x
.
为什么 R 会这样,我可以更改它或绕过它吗? 如果我可以 运行 %,%
中的代码 before 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)
}
}
我很确定这将是一个致命的罪过,对吧?我无法 确切地 看到它会如何搞砸任何事情,但我的程序员感官的刺痛告诉我这 不 适合分享像包裹之类的东西... 这会有多糟糕?
关于你的第一个问题,关于 *tmp*
(可能与你的第二个问题有关):
来自 Section 3.4.4 of the R Language definition:
Assignment to subsets of a structure is a special case of a general mechanism for complex assignment:
x[3:5] <- 13:15
The result of this command is as if the following had been executed
`*tmp*` <- x
x <- "[<-"(`*tmp*`, 3:5, value=13:15)
rm(`*tmp*`)
Note that the index is first converted to a numeric index and then the elements are replaced sequentially along the numeric index, as if a
for
loop had been used. Any existing variable called*tmp*
will be overwritten and deleted, and this variable name should not be used in code.
The same mechanism can be applied to functions other than
[
. The replacement function has the same name with<-
pasted on. Its last argument, which must be calledvalue
, is the new value to be assigned.
我可以想象你的第二个问题与“好像”代码的第一步有关:如果 R 在内部尝试评估 <code>*tmp*
<- x,可能无法阻止此时尝试评估 x
...
如果你想深入挖掘,我认为用于处理“复杂赋值”(内部注释中似乎这样称呼)的内部评估代码大约在 here ...