在Scala中有效实现Catamorphisms

时间:2017-01-30 21:10:03

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

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

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?

2 个答案:

答案 0 :(得分:1)

如果你在列表上实现了一个catamorphism,那么在Haskell中我们称之为foldr。我们知道foldr没有尾递归定义,但foldl有。{1}}。因此,如果你坚持使用尾部重复程序,那么正确的做法是反转list参数(以线性时间尾递归),然后使用foldl代替foldr。< / p>

您的示例使用更简单的自然数据类型(真正的&#34;高效&#34;实现将使用机器整数,但我们同意将其搁置一旁)。你的一个自然数字的反转是什么?只是数字本身,因为我们可以把它看作一个列表,每个节点都没有数据,所以我们无法区分它反转的时候!那等同于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)    
  } 

那会吗?

答案 1 :(得分:0)

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

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

  1. 到目前为止你已经计算了什么
  2. 你还剩下什么。
  3. 此状态的类型取决于您正在进行折叠的类型的结构,在折叠中的任何位置,您在树的某个节点处,并且您需要记住“其余部分”的树结构那个树”。 本文介绍了如何机械地计算该状态类型。如果您对列表执行此操作,则可以获得需要跟踪的状态

    1. 该操作对所有先前的值运行。
    2. 要处理的元素列表。
    3. 这正是foldl追踪的内容,因此foldlfoldr可以被赋予相同类型的巧合。