如何不陷入R的'懒惰评价陷阱'

jhi*_*hin 16 design-patterns functional-programming r lazy-evaluation

"R通过了承诺,而不是价值观.承诺在首次评估时被强制执行,而不是在通过时.",请参阅G. Grothendieck的回答.另请参阅Hadley的书中的这个问题.

在简单的例子中

> funs <- lapply(1:10, function(i) function() print(i))
> funs[[1]]()
[1] 10
> funs[[2]]()
[1] 10
Run Code Online (Sandbox Code Playgroud)

可以考虑这种不直观的行为.

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

忽略这样的问题导致难以调试问题,因为所有计算都顺利进行 - 除了结果不正确.只有对结果的明确验证才能揭示问题.

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

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

Bro*_*ieG 11

您正在使用隐式参数创建函数,这不一定是最佳实践.在您的示例中,隐式参数是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
Run Code Online (Sandbox Code Playgroud)

在这里,我们通过使用显式指定函数的参数Curry.请注意,我们可以print直接使用咖喱,但这里没有用于说明目的.

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

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

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
Run Code Online (Sandbox Code Playgroud)

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


mri*_*rip 6

正如其他人指出的那样,这可能不是R中最好的编程风格.但是,一个简单的选择就是养成强迫一切的习惯.如果你这样做,意识到你不需要实际调用force,只需要评估符号即可.为了减少丑陋,你可以尝试启动这样的函数:

myfun<-function(x,y,z){
   x;y;z;
   ## code
}
Run Code Online (Sandbox Code Playgroud)

  • `force` 的冗长或“丑陋”正是它的目的:它使 _intent_ 明确——强加了严格的评估。 (2认同)

Luk*_*ney 5

有一些工作正在进行中以改进R的高阶函数,例如应用函数,Reduce等,以及处理这类情况.是否在几周内发布R 3.2.0取决于变化的破坏程度.应该在一周左右变得清晰.