【问题标题】:Efficient implementation of Catamorphisms in Scala在 Scala 中高效实现 Catamorphisms
【发布时间】:2017-01-30 21:10:02
【问题描述】:

对于表示自然数的数据类型:

sealed trait Nat
case object Z extends Nat
case class S(pred: Nat) extends Nat

在 Scala 中,这是实现相应catamorphism 的基本方法:

  def cata[A](z: A)(l: Nat)(f: A => A): A = l match {
    case Z => z
    case S(xs) => f( cata(z)(xs)(f) )    
  } 

但是,由于对 cata 的递归调用不在尾部位置,这很容易触发堆栈溢出。

有哪些替代实施选项可以避免这种情况?我宁愿不走F-algebras 的路线,除非代码最终呈现的界面看起来和上面一样简单。

编辑:看起来这可能直接相关:Is it possible to use continuations to make foldRight tail recursive?

【问题讨论】:

  • 不应该像这样制作 cmets,但到底是什么......“有趣的问题”。期待回复。

标签: scala algebraic-data-types category-theory recursion-schemes catamorphism


【解决方案1】:

如果你在列表上实现变态,这就是在 Haskell 中我们称之为foldr 的东西。我们知道foldr 没有尾递归定义,但foldl 有。因此,如果您坚持使用尾递归程序,正确的做法是反转列表参数(尾递归,线性时间),然后使用 foldl 代替 foldr。

您的示例使用更简单的自然数据类型(真正“高效”的实现将使用机器整数,但我们同意将其放在一边)。你的一个自然数的倒数是多少?只是数字本身,因为我们可以把它看成一个列表,每个节点都没有数据,所以倒过来的时候就分不清了! foldl 的等价物是什么?是程序(请原谅伪代码)

  def cata(z, a, f) = {
    var x = a, y = z;
    while (x != Z) {
      y = f(y);
      x = pred(x)
    }
    return y
  }

或者作为 Scala 尾递归,

  def cata[A](z: A)(a: Nat)(f: A => A): A = a match {
    case Z => z
    case S(b) => cata( f(z) )(b)(f)    
  } 

这样可以吗?

【讨论】:

    【解决方案2】:

    是的,这正是论文 Clowns to the left of me, jokers to the right (Dissecting Data Structures) 中的激励示例(更新,更好,但此处为非免费版本 http://dl.acm.org/citation.cfm?id=1328474)。

    基本思想是你想把你的递归函数变成一个循环,所以你需要找出一个跟踪过程状态的数据结构,也就是

    1. 到目前为止您计算的结果
    2. 你还有什么事情要做。

    这种状态的类型取决于你正在折叠的类型的结构,在折叠中的任何一点你都在树的某个节点处,你需要记住“其余部分的树结构”那个树”。 该论文展示了如何机械地计算该状态类型。如果您对列表执行此操作,您会得到需要跟踪的状态是

    1. 该操作在所有以前的值上运行。
    2. 要处理的元素列表。

    这正是foldl 跟踪的内容,所以foldl 和foldr 可以被赋予相同的类型有点巧合。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-27
      • 1970-01-01
      • 1970-01-01
      • 2010-11-07
      • 2016-02-15
      • 2017-03-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多