R如何评估这些奇怪的表达式?

8
我试图在R语言中实现Python 3风格的解包赋值(例如,a, *b, c = [1,2,3], "C"),虽然我接近成功了(你可以在这里查看我的代码),但最终遇到了一些(奇怪的)问题。
我的代码应该像这样工作:
a %,*% b %,% c <- c(1,2,3,4,5)

并将会分配 a = 1b = c(2,3,4)c = 5。为了让这个工作正常,我必须定义:
`%,%` <- function(lhs, rhs) {
   ...
}

`%,%<-` <- function(lhs, rhs, value) {
   ...
}

(同时还有%,*%%,*%<-,它们是前面函数的轻微变体)。

第一个问题:为什么R在lhs参数中将*tmp*替换为其他值

据我了解,R首先从左到右评估此代码(即,从ac),直到到达最后一个%,%,然后从右到左返回,途中依次赋值。但我注意到的第一件奇怪的事情是,当我在类似于x %infix% y < - z这样的语句中执行match.call()substitute(lhs)时,它说%infix%lhs参数的输入是*tmp*,而不是ax等。

对我来说,这太奇怪了,我在R手册或文档中找不到任何提及它的信息。实际上我在我的代码中利用了这种奇怪的约定(即,在赋值语句的右侧不显示此行为,因此我可以使用*tmp*作为变量标记,使得%,%在赋值语句的右侧表现出不同的行为),但我不知道为什么会这样。

第二个问题:为什么R在任何其他操作之前检查对象是否存在

我的第二个问题是导致我的代码最终无法工作的原因。我注意到,如果你在任何赋值语句的左边使用一个变量名字,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)
  }
}

我非常确定这将是一种致命的罪过,对吗?我不太确定它会如何搞砸任何事情,但我的程序员直觉告诉我这不适合在像软件包这样的东西中分享... 那么这有多糟糕?


1
你需要在LHS上使用替换函数吗?(你的第二个例子函数是否应该是%,%<-?) - Ben Bolker
哎呀,是的,我会纠正它。 - Zeke
1
这看起来相当糟糕/危险 :-( 你肯定可以在github包中使用它,但我敢打赌它很难被CRAN接受...虽然CRAN政策中没有关于掩盖常用的base函数的规定(tidyverse已经做了很多这方面的工作,尽管没有像<-这样基本的东西 - 另一方面,与您的代码不同,它打算以不兼容的方式改变行为(例如filter())。 - Ben Bolker
1个回答

5

针对您的第一个问题,关于*tmp*(可能与您的第二个问题有关):

来自R语言定义的3.4.4节

对结构的子集进行赋值是复杂赋值机制的一个特例:

x[3:5] <- 13:15

这个命令的结果就像执行了以下操作。
`*tmp*` <- x
x <- "[<-"(`*tmp*`, 3:5, value=13:15)
rm(`*tmp*`)

请注意,索引首先被转换为数字索引,然后元素按照数字索引顺序逐个替换,就像使用了一个“for”循环一样。任何名为“*tmp*”的现有变量将被覆盖并删除,代码中不应使用此变量名。
相同的机制可以应用于除“[”之外的其他函数。替换函数具有与<-粘贴在一起的相同名称。它的最后一个参数(必须称为value)是要分配的新值。
我可以想象你的第二个问题与“as if”代码的第一步有关:如果R在内部尝试评估*tmp* <- x,那么在这一点上防止尝试评估x可能是不可能的......
如果您想进一步挖掘,我认为用于处理“复杂赋值”的内部评估代码(似乎在内部注释中称为如此)在此处附近...

糟糕,我没看到这个!我查了一下,发现我可以通过这样做来实现我想要的行为: `<-` <- function(x, value) { ...; do.call(base::`<-`, c(x=substitute(x), value = substitute(value))) }。但是这样做通常被认为是非常不好的,对吧?我想这对于共享使用来说是不可接受的。 - Zeke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接