16

考虑以下简单函数:

f <- function(x, value){print(x);print(substitute(value))}

参数x最终将由 评估print,但value永远不会。所以我们可以得到这样的结果:

> f(a, a)  
Error in print(x) : object 'a' not found  
> f(3, a)  
[1] 3  
a  
> f(1+1, 1+1)  
[1] 2  
1 + 1  
> f(1+1, 1+"one")  
[1] 2  
1 + "one"

一切如预期。

现在考虑替换函数中的相同函数体:

'g<-' <- function(x, value){print(x);print(substitute(value))}

(单引号应该是花式引号)

让我们尝试一下:

> x <- 3  
> g(x) <- 4  
[1] 3  
[1] 4  

目前没有什么异常...

> g(x) <- a  
Error: object 'a' not found  

这是出乎意料的。名称a应打印为语言对象。

> g(x) <- 1+1  
[1] 4  
1 + 1  

这没关系,因为x' 以前的值是4。请注意未计算传递的表达式。

最终测试:

> g(x) <- 1+"one"  
Error in 1 + "one" : non-numeric argument to binary operator  

等一下……它为什么要尝试评估这个表达式?

那么问题是:错误还是功能?这里发生了什么?我希望一些 guru 用户能够对 R 的承诺和惰性评估有所了解。或者我们可能会得出结论,这是一个错误。

4

3 回答 3

12

我们可以将问题简化为一个稍微简单的例子:

g <- function(x, value)
'g<-' <- function(x, value) x
x <- 3

# Works
g(x, a)
`g<-`(x, a)

# Fails
g(x) <- a

这表明 R 在评估替换函数时正在做一些特殊的事情:我怀疑它会评估所有参数。我不知道为什么,但是 C 代码中的注释(https://github.com/wch/r-source/blob/trunk/src/main/eval.c#L1656https://github.com /wch/r-source/blob/trunk/src/main/eval.c#L1181)建议可能是为了确保其他中间变量不会被意外修改。

Luke Tierney 对当前方法的缺点进行了长篇评论,并说明了可以使用替换函数的一些更复杂的方法:

这里的方法有两个问题:

复杂赋值中的复杂赋值,如 f(x, y[] <- 1) <- 3,可能会导致外部赋值的临时变量值被覆盖,然后被内部赋值删除。这可以通过使用多个临时变量或对这个变量使用一个承诺来解决,就像对 RHS 所做的那样。然后可能需要调整错误消息中替换函数调用的打印。

使用形式f(g(x, z), y) <- w的赋值z将计算两次,一次用于调用g(x, z) ,一次用于调用替换函数g<-。可以通过使用 Promise 来解决这个问题。使用更多的临时变量是行不通的,因为它会弄乱使用替代和/或非标准评估的替换函数(并且有一些包可以做到这一点——igraph 就是其中之一)。

于 2013-03-06T22:27:44.833 回答
8

我认为关键可以在从第 1682 行"eval.c"开始的这条评论中找到(并且紧随其后的是对赋值操作的 RHS 的评估):

/* It's important that the rhs get evaluated first because
assignment is right associative i.e. a <- b <- c is parsed as
a <- (b <- c). */

PROTECT(saverhs = rhs = eval(CADR(args), rho));

我们期望如果我们这样做,g(x) <- a <- b <- 4 + 5两者都会被赋值;这实际上就是发生的事情。ab9

显然,R确保这种一致行为的方式是始终先评估分配的 RHS,然后再执行其余的分配。如果该评估失败(例如当您尝试类似的操作时g(x) <- 1 + "a"),则会引发错误并且不会进行任何分配。

于 2013-03-07T07:00:40.263 回答
4

我将在这里冒险,所以请有更多知识的人随时评论/编辑。

请注意,当您运行

'g<-' <- function(x, value){print(x);print(substitute(value))}
x <- 1
g(x) <- 5

副作用是 5 被分配给x. 因此,必须对两者进行评估。但是如果你再跑

'g<-'(x,10)

和 10的值x都被打印出来,但 的值x保持不变。

推测:

因此,解析器会区分您是否g<-在进行实际分配的过程中调用,以及何时直接调用g<-

于 2013-03-06T17:13:05.157 回答