为什么替换函数的参数不会被懒惰地评估?

时间:2013-03-06 16:17:58

标签: r promise

考虑以下简单功能:

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  

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

问题是:错误还是功能?这里发生了什么?我希望一些大师用户会对R上的承诺和懒惰评估有所了解。或者我们可能只是认为这是一个错误。

3 个答案:

答案 0 :(得分: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<-。它   也许可以通过使用promises来解决这个问题。   使用更多的临时工作不会起作用,因为它会搞砸   替换函数使用替换和/或   非标准评估(并且有一些包   那个 - igraph是一个。)

答案 1 :(得分:8)

我认为可以在line 1682 of "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 ab,则会为其分配值9;这实际上是会发生什么。

显然,R 确保这种一致行为的方式是在执行其余任务之前始终首先评估任务的RHS。如果该评估失败(就像您尝试g(x) <- 1 + "a"之类的那样),则会抛出错误并且不会进行任何分配。

答案 2 :(得分:4)

我会在这里走出困境,所以请有更多知识的人随意评论/编辑。

请注意,当您运行

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

副作用是将{5}分配给x。因此,两者都必须进行评估。但是如果你再运行

'g<-'(x,10)

打印x和10的值,但x的值保持不变。

<强>猜测:

因此,解析器可以区分您在进行实际作业时是否致电g<-,以及何时直接致电g<-