如何不落入R'lazy evaluation trap'

How to not fall into R's 'lazy evaluation trap'

"R 通过 promises, not values. The promise is forced when it is first evaluated, not when it is passed.", see this answer by G. Grothendieck. Also see this question 指的是哈德利的书。

简单的例子如

> funs <- lapply(1:10, function(i) function() print(i))
> funs[[1]]()
[1] 10
> funs[[2]]()
[1] 10

可以将这种不直观的行为考虑在内。

但是,我发现自己在日常开发中经常掉入这个陷阱。我遵循一种相当函数式的编程风格,这意味着我经常有一个函数 A 返回一个函数 B,其中 B 在某种程度上取决于调用 A 的参数。依赖关系不像上面的例子那么容易看出,因为计算复杂,参数多

忽视这样的问题会导致难以调试问题,因为所有计算 运行 都很顺利 - 除了结果不正确。只有对结果进行显式验证才能揭示问题所在。

最重要的是,即使我注意到了这样的问题,我也永远不确定哪些变量需要 force 哪些不需要。

我怎样才能确保不落入这个陷阱?是否有任何编程模式可以防止这种情况或至少确保我注意到存在问题?

您正在创建带有隐式参数的函数,这不一定是最佳做法。在您的示例中,隐式参数是 i。另一种返工方法是:

library(functional)
myprint <- function(x) print(x)
funs <- lapply(1:10, function(i) Curry(myprint, i))
funs[[1]]()
# [1] 1
funs[[2]]()
# [1] 2

在这里,我们使用 Curry 显式指定函数的参数。请注意,我们本可以直接柯里化 print,但此处并未出于说明目的。

Curry 使用预先指定的参数创建函数的新版本。这使得参数规范明确,并避免了您 运行 遇到的潜在问题,因为 Curry 强制评估(有一个版本没有,但它在这里无济于事)。

另一种选择是捕获父函数的整个环境,复制它,并使其成为新函数的父环境:

funs2 <- lapply(
  1:10, function(i) {
    fun.res <- function() print(i)
    environment(fun.res) <- list2env(as.list(environment()))  # force parent env copy
    fun.res
  }
)
funs2[[1]]()
# [1] 1
funs2[[2]]()
# [1] 2

但我不推荐这样做,因为您可能会复制一大堆您甚至可能不需要的变量。更糟糕的是,如果你有创建函数的嵌套函数层,这会变得更加复杂。这种方法的唯一好处是您可以继续您的隐式参数规范,但同样,这对我来说似乎是不好的做法。

正如其他人指出的那样,这可能不是 R 中最好的编程风格。但是,一个简单的选择就是养成强制一切的习惯。如果这样做,请意识到您不需要实际调用 force,只需计算符号即可。为了让它不那么难看,你可以养成这样启动函数的习惯:

myfun<-function(x,y,z){
   x;y;z;
   ## code
}

目前正在进行一些工作来改进 R 的高阶函数,例如应用函数、Reduce 等,以处理此类情况。这是否会导致 R 3.2.0 在几周内发布取决于这些变化的破坏性。应该会在一周左右变得清晰。

R 有一个函数可以帮助防止惰性求值,在类似闭包创建的情况下:forceAndCall().

来自联机 R 帮助文档:

forceAndCall is intended to help defining higher order functions like apply to behave more reasonably when the result returned by the function applied is a closure that captured its arguments.