奇怪的mapply行为:我错过了什么?

时间:2011-12-09 03:24:36

标签: r

以下代码无法正常工作:

a <- list(0, 1)
b <- list(0, 1)

# return a linear function with slope `a` and intercept `b`.
f <- function(a, b) function(x) a*x + b

# create a list of functions with different parameters.
fs <- mapply(f, a, b)

# test
fs[[1]](3)
# [1] 4  # expected zero!
fs[[2]](3)
# [1] 4

谁能告诉我为什么?

注意:我找到了一种解决方法,所以我不是在寻找一种不同的方法来达到预期的效果。但我很好奇为什么这种特殊方法不起作用。


更新

从R 3.2.0开始,现在按预期工作:

a <- list(0, 1)
b <- list(0, 1)
f <- function(a, b) function(x) a*x + b
fs <- mapply(f, a, b)

# test
fs[[1]](3)
# [1] 0 
fs[[2]](3)
# [1] 4

2 个答案:

答案 0 :(得分:9)

这是延迟评估的结果 - 所有参数都在调用树下传递,以避免不必要的执行并保持在此挂起状态,直到R确信它们已被使用。

在您的代码中,您只需使用与b相同的承诺填充函数;然后他们都致力于最后一对价格。正如@Tommy已经表明的那样,解决方案是通过在定义函数之前“使用”值来强制承诺。

答案 1 :(得分:8)

[更新] 我的初步分析是正确的,但结论是错误的:)让我们在分析后得出结论。

以下是一些展示效果的代码:

x <- lapply(1:3, function(x) sys.frame(sys.nframe()))
x[[1]] # An environment
x[[2]] # Another environment
x[[3]] # Yet nother environment
x[[1]]$x  # 3!!! (should be 1)
x[[2]]$x  # 3!!  (should be 2)
x[[3]]$x  # 3 as expected

# Accessing the variable within the function will "fix" the weird behavior:
x <- lapply(1:3, function(x) {x; sys.frame(sys.nframe())})
x[[1]]$x  # 1
x[[2]]$x  # 2
x[[3]]$x  # 3

所以在你的情况下解决方法:

f <- function(a, b) { a;b; function(x) a*x + b }

顺便说一下,正如@James所说,有一个force函数可以更明确地访问变量:

f <- function(a, b) { force(a);force(b); function(x) a*x + b }

<强>结论

好吧,正如@mbq和@hadley所说,这是由于懒惰的评估。使用简单的for循环更容易显示:

fs <- list(); for(i in 1:2) fs[[i]] <- f(a[[i]], b[[i]])

函数f的{​​{1}}参数获取xa[[i]] }),但整个表达式以及0a存在的环境。当您访问i时,会对其进行评估,因此在评估时会使用x。如果从调用i开始,for循环已经移动,则会得到“错误”的结果......

最初我说这是由于f中的一个错误,但事实并非如此。 ...但由于我讨厌错误,我可以指出* apply DOES在这些情况下有一个错误(或者更多的不一致):

*apply

如上所述,lapply(11:12, function(x) sys.call()) #[[1]] #FUN(11:12[[1L]], ...) # #[[2]] #FUN(11:12[[2L]], ...) lapply(11:12, function(x) function() x)[[1]]() # 12 lapply(11:12, function(x) function() x)[[2]]() # 12 代码表示它使用lapply调用该函数。如果您评估“稍后”, 仍然会获得值11:12[[1L]] - 但实际上您获得了11

这可能是 ,因为12由于性能原因而在C代码中实现并且有点作弊,所以它显示的表达式不是得到评估的表达式 - ergo,一个bug ......

QED