是否有一种在Scala中创建尾递归函数的通用方法?

时间:2017-03-20 18:22:07

标签: scala tail-recursion

在检查英特尔的BigDL repo时,我偶然发现了这种方法:

private def recursiveListFiles(f: java.io.File, r: Regex): Array[File] = {
  val these = f.listFiles()
  val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
  good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_, r))
}

我注意到它不是尾递归并且决定编写一个尾递归版本:

private def recursiveListFiles(f: File, r: Regex): Array[File] = {
@scala.annotation.tailrec def recursiveListFiles0(f: Array[File], r: Regex, a: Array[File]): Array[File] = {
  f match {
    case Array() => a
    case htail => {
      val these = htail.head.listFiles()
      val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
      recursiveListFiles0(these.filter(_.isDirectory)++htail.tail, r, a ++ good)
    }
  }
}
recursiveListFiles0(Array[File](f), r, Array.empty[File])
}

与我习惯的相比,这使得难以将File转换为Array [File]的概念增加了另一个深度。

具有以下成员的数据类型的递归背后的理论是什么?

def listTs[T]: T => Traversable[T]

1 个答案:

答案 0 :(得分:3)

简短回答

如果你概括了这个想法并将其视为一个monad(用于任意类型params的多态事物),那么你将无法实现尾递归实现。

Trampolines试图通过提供一种评估递归计算而不会溢出堆栈的方法来解决这个问题。一般的想法是创建一对(结果,计算)的流。因此,在每个步骤中,您必须将计算结果返回到该点,并使用函数创建下一个结果(又名thunk)。

来自Rich Dougherty的博客:

  

蹦床是一个反复运行功能的循环。每个功能,   称为thunk,返回循环运行的下一个函数。该   蹦床从来不会一次运行多个thunk,所以如果你打破了   把你的程序变成足够小的thunks并从中弹出每一个   蹦床,那么你可以肯定堆栈不会变得太大。

更多+参考资料

在分类意义上,此类数据类型背后的理论与Cofree Monadsfold以及unfold函数密切相关,通常与Fixed point types密切相关。

请参阅Rob Norris的这篇精彩演讲:Fun and Games with Fix Cofree and Doobie,讨论与您的问题非常相似的用例。

这篇关于免费monad和Trampolines的文章也与你的第一个问题有关:Stackless Scala With Free Monads

另见Matryoshka docs的这一部分。 Matryoshka是一个Scala库,它围绕FixedPoint类型的概念实现monad。