【问题标题】:using callCC with higher-order functions in R在 R 中使用带有高阶函数的 callCC
【发布时间】:2013-10-26 03:51:39
【问题描述】:

我正在尝试弄清楚如何获取 R 的 callCC 函数,以对函数进行短路评估,以使用 lapply 和 Reduce 等函数。

动机

这将使 Reduce 和 lapply 具有 > O(n) 的渐近效率,通过允许您 提前退出计算。

例如,如果我在列表中搜索一个值,我可以在列表中映射一个“finder”函数,然后第二个发现它 lapply 停止运行并返回该值(很像打破循环,或使用 return 语句提前爆发)。

问题是我在编写 lapply 和 Reduce 应该使用 callCC 需要的样式的函数时遇到问题。

示例

假设我正在尝试编写一个函数来在列表中查找值“100”:相当于

imperativeVersion <- function (xs) {
    for (val in xs) if (val == 100) return (val)
}

传递给 lapply 的函数如下所示:

find100 <- function (val) { if (val == 100) SHORT_CIRCUIT(val)  }
functionalVersion <- function (xs) lapply(xs, find100)

这(显然)崩溃了,因为尚未定义短路功能。

callCC( function (SHORT_CIRCUIT) lapply(1:1000, find100) )

问题是这也会崩溃,因为在定义 find100 时短路功能不存在。我希望类似的东西可以工作。

以下内容有效,因为 SHORT_CIRCUIT 是在创建传递给 lapply 的函数时定义的。

callCC(
    function (SHORT_CIRCUIT) {
        lapply(1:1000, function (val) {
             if (val == 100) SHORT_CIRCUIT(val)
        })
)

如何在传递给 lapply 的函数中定义 SHORT_CIRCUIT 而无需像上面那样内联定义它?

我知道这个示例可以使用循环、reduce 或任何其他数量的方式来实现。我正在寻找一个解决方案来解决将 callCC 与 lapply 和 Reduce 结合使用的问题。

如果我含糊不清或需要任何澄清,请在下面发表评论。我希望有人可以帮助解决这个问题:)

编辑一: 方法应该是“生产质量”;没有分离功能或类似的黑魔法。

【问题讨论】:

    标签: r functional-programming continuations


    【解决方案1】:

    不知道能不能用,但是:

        find100 <- "function (val) { if (val == 100) SHORT_CIRCUIT(val)  }"
        callCC( function (SHORT_CIRCUIT) lapply(1:1000, eval(parse(text = find100))) )
        #[1] 100
    

    【讨论】:

    • 感谢您的回答,但对于我的目的而言,evalparse 在 hacky 方面太过分了。将函数解析为字符串并不总是可行的,并且有关定义函数的封闭环境的所有信息都将丢失。 (这会破坏许多功能)。
    【解决方案2】:

    我找到了解决这个问题的方法:

    find100 <- function (val) {
        if (val == 100) SHORT_CIRCUIT(val)
    }
    
    short_map <- function (fn, coll) {
    
    
        callCC(function (SHORT_CIRCUIT) {
    
            clone_env <- new.env(parent = environment(fn))
            clone_env$SHORT_CIRCUIT <- SHORT_CIRCUIT
    
            environment(fn) <- clone_env
            lapply(coll, fn)
    
        })
    }
    
    short_map(find100, c(1,2,100,3))
    

    使高阶函数与 callCC 一起工作的技巧是在继续执行程序的其余部分之前将短路函数分配到输入函数环境中。我克隆了环境以避免意外的副作用。

    【讨论】:

      【解决方案3】:

      您可以使用 R 中的元编程来实现这一点。 @alexis_laz 的方法实际上已经是元编程。 但是,他使用的字符串很脏,而且容易出错。所以你很好地拒绝了它。

      处理@alexis_laz 方法的正确方法是在代码级别上争论不休。在基础 R 中,这是使用 substitute() 完成的。但是有更好的软件包,例如rlang 哈德利·威克姆。但是我给你一个基本的 R 解决方案(更少的依赖)。

      lapply_ <- function(lst, FUN) {
        eval.parent(
          substitute(
            callCC(function(return_) {
              lapply(lst_, FUN_)
            }), 
            list(lst_ = lst, FUN_=substitute(FUN)))
      }
      

      你的SHORT_CIRCUIT 函数实际上是一个更通用的控制流return 函数(或一个break 函数,它需要一个参数来返回它)。因此,我称之为return_

      我们希望有一个lapply_ 函数,其中我们可以在FUN= 部分使用return_break,而不是通常的lapply()

      正如你所展示的,这是目标:

      callCC(
        function (return_) {
          lapply(1:1000, function (x) if (x == 100) return_(x))
        }
      )
      

      只是有一个问题,我们希望能够概括这个表达式。 我们想要

      callCC(
        function(return_) lapply(lst, FUN_)
      )
      

      我们可以在我们为FUN_return_ 提供的函数定义中使用。 但是,只有将函数定义代码插入到此表达式中,我们才能让函数定义看到return_。 这正是@alexis_laz 尝试使用字符串和评估。 或者您通过操作环境变量来做到这一点。

      我们可以使用substitute(expr, replacer_list) 安全地实现文字代码的插入,其中expr 是要操作的代码,replacer_list 是用于替换代码的查找表。 通过substitute(FUN),我们将FUN= 给出的文字代码用于lapply_,而不对其进行评估。此表达式返回文字引用代码(比@alexis_laz 方法中的字符串更好)。

      大的substitute 命令说:“取表达式callCC(function(return_) lapply(lst_, FUN_)) 并将此表达式中的lst_ 替换为collFUN_ 给出的列表为FUN 给出的文字引用表达式。

      然后在父环境 (eval.parent()) 中评估这个被替换的表达式,这意味着:结果表达式替换了 lapply_() 调用,并在它被放置的位置执行。

      这样使用eval.parent()(或eval( ... , envir=parent.frame()))是万无一失的。 (否则,tidyverse 包不会是生产级别的......)。

      所以通过这种方式,你可以泛化callCC()的调用。

      lapply_(1:1000, FUN=function(x) if (x==100) return_(x))
      ## [1] 100
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-23
        • 2020-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多