【问题标题】:Why are arguments to replacement functions not evaluated lazily?为什么替换函数的参数不被懒惰地评估?
【发布时间】:2013-03-08 00:36:48
【问题描述】:

考虑以下简单函数:

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 的 promise 和惰性评估有所了解。或者我们可能会认为这是一个错误。

【问题讨论】:

  • 我可能遗漏了一些非常基本的东西,但在我看来,您的两个函数的行为方式完全相同。当你们所有人f(a, a) 时,您会收到您期望的错误消息。当您致电g(x) &lt;- a 时,您会收到完全相同的错误消息,但您并不期望它。这是为什么呢?
  • 我认为问题在于您认为解释器实际上在做什么。请注意,'g&lt;-'(x, 1+"one") 有效
  • 第一条错误消息来自print(x),而不是来自print(substitute(value))。关键是:a 没有定义。在对f 的第二次调用中,符号a 即使未定义也是可以的,并被打印为语言对象。与电话g(x) &lt;- a比较。
  • @Dason,所以这意味着g(x) &lt;- y 不等于y &lt;- 'g&lt;-'(x,y)。为什么不?当我们在第一次调用中使用替换函数时,还会发生什么?
  • 这可能是因为当R解析g(x) &lt;- a将其转换为"g&lt;-"(x,a)时,它会评估a

标签: r promise


【解决方案1】:

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

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

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

# Fails
g(x) <- a

这表明 R 在评估替换函数时正在做一些特殊的事情:我怀疑它会评估所有参数。我不知道为什么,但 C 代码中的 cmets(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[] &lt;- 1) &lt;- 3,可能会导致值临时 用于覆盖外部赋值的变量和 然后被内部删除。这可以通过以下方式解决 使用多个临时人员或为此使用承诺 变量,就像为 RHS 所做的那样。打印的 错误消息中的替换函数调用可能需要 有待调整。

使用f(g(x, z), y) &lt;- w 形式的赋值 的z 将计算两次,一次用于调用g(x, z) 一次用于调用替换函数g&lt;-。它 可能可以通过使用承诺来解决这个问题。 使用更多的临时人员是行不通的,因为它会搞砸 使用替代和/或的替换函数 非标准评估(并且有一些软件包可以 那 -- igraph 是其中之一)。

【讨论】:

  • 很高兴你开始研究这个。你能看看我的回答,让我知道你的想法吗?
  • @hadley 两个行号引用似乎都已过时 :( 你有没有机会知道哪些 cmets 是针对的?我仍然看到你在其中引用的 Luke 的报价
【解决方案2】:

我认为关键可以在此评论中找到,从 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) &lt;- a &lt;- b &lt;- 4 + 5ab 都将被赋值为9;这实际上就是发生的事情。

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

【讨论】:

  • 上面引用的块来自applydefine,由do_set调用(实现&lt;-=&lt;&lt;-)。当我读到它时,applydefine 在 LHS 是一个语言对象时被调用(比如g(x),但不是当它是一个名称对象时(比如x)。具体参见"eval.c" 的第 1844 和 1869 行。可以C-literate type请确认我的解释是否正确?
  • 当我们使用链式作业时,事情变得更加有趣: @987654338 @ @987654339 @ @98765440 @9876540 @987654341 @&gt; y[1] 2&gt; mode(y)[1] "numeric"
  • @Ferdinand.kraft -- 甚至是这个:'g&lt;-' &lt;- function(x, value){deparse(substitute(value))}; x &lt;- 0; y &lt;- g(x) &lt;- 100 + 100; y; xx 获得了它的价值,这对我来说很有意义,但我仍然不确定我是否看到了评估所有部分的顺序。看起来&lt;- 1+1 几乎被解析和评估了两次,一次作为g(x) &lt;- 1 + 1 的RHS,一次作为y &lt;- g(x) &lt;- 1 + 1 的RHS 的RHS(如果这有意义的话)。无论如何,感谢这个有趣的问题!
  • 我相信我已经弄清楚这里发生了什么。赋值表达式g(x) &lt;- 1+1 有一个result,它是RHS 的求值,不管'g&lt;-' 函数做什么。例如,尝试一个虚拟函数'g&lt;-' &lt;- function(x, value){0}。然后输入(g(x) &lt;- 1+1) == 2。这解释了为什么g(x) &lt;- 1+"one" 会触发错误。这不是因为 RHS 在函数内部 被评估,而是它在外部被评估,由赋值函数本身,它必须返回这个值,以防万一它是一个赋值链。如果不是,则结果将被丢弃。
  • @JoshO'Brien,当您编写y &lt;- g(x) &lt;- 100 + 100 时,您是否期望(就像我一样)y 被赋予与x 相同的值?请参阅我上面的评论。我相信这是一个错误的假设。 y 被分配了 g(x) &lt;- 100 + 100result,这是函数 'g&lt;-' 返回的 RHS 不管。一个更普通的例子是:w &lt;- 42; wn &lt;- names(w) &lt;- "Answer"wn 将只接收字符向量。
【解决方案3】:

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

请注意,当您运行时

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

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

'g<-'(x,10)

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

推测:

因此解析器会区分您是在进行实际分配的过程中调用g&lt;-,还是直接调用g&lt;-

【讨论】:

  • x 的值跟在g(x) &lt;- 1 + 1 之后——不是2——是否破坏了这个假设? 看起来在这种情况下,RHS 已分配但未评估。
  • @JoshO'Brien 好吧,这肯定强烈暗示我没有看到全貌,这该死的肯定。也许1+1 可以评估,因为它不涉及对象查找...?
  • 嗯,我认为你绝对是在正确的轨道上。如需平行证据,请比较 e1 &lt;- parse(text="g(x) &lt;- 1 + 'a'"); e2 &lt;- parse(text="'g&lt;-'(x, 1 + 'a')"); as.list(e1[[1]]); as.list(e2[[1]])。我的强烈猜测是,如果不深入研究 &lt;-REPL 的 C 级实现,我们不会在这方面取得更多进展。
  • 有一件事是g(x) = 5 的等价物是x = 'g&lt;-'(x, 5)print(substitute(value)) 也返回 substitute(value),即未计算表达式的解析树。另一方面,'g&lt;-'= function(x, value) value 将强制评估价值。此外,关键参与者是第一个和最后一个值,因此'g&lt;-' = function(x, ..., value) value 是通常的“替换”范式。可能像 'g&lt;-' = function(x, FUN, ..., value) FUN(value)g(x, abs) = -2g(x, sqrt) = 2 一样使用
猜你喜欢
  • 2014-10-10
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 2017-01-13
  • 1970-01-01
  • 2016-05-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多