如何将递归转换为折叠

时间:2014-05-18 03:21:53

标签: scala recursion

根据Erik Meijer的说法,作为函数式程序员,我们都知道我们应该使用fold来代替递归。你如何转换以下使用折叠?我可以看到返回的一种方式,但在fp中也应该避免返回。谢谢!

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = {
  zomOldList match {
    case Nil =>
      throw original
    case head :: tail =>
      try {
        head(string)
      } catch {
        case ex: Exception =>
          tryOld(string, original, tail)
      }
  }
}

3 个答案:

答案 0 :(得分:3)

您可以使用foldRight利用函数作为值来实现此目的:

import util.control.NonFatal
def tryOld(string: String, original: Exception, zomOldList: List[String ⇒ Double]): Double = {
  val unhandled: String ⇒ Double = _ ⇒ throw original
  zomOldList.foldRight(unhandled) { (f, z) ⇒
    x ⇒ try { f(x) } catch { case NonFatal(_) ⇒ z(x) }
  }(string)
}

注意我们在这里使用NonFatal来避免捕获我们不应该捕获的异常。您可以通过不直接使用异常以更优雅的方式编写此代码。

答案 1 :(得分:2)

您无法通过折叠实现此功能。折叠循环遍历集合的每个元素,而tryOld有时会提前终止。您可以利用Stream的懒惰,并根据collectFirstTry来实施:

import scala.util.Try

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = 
  zomOldList.toStream.map(x => Try(x(string))) collectFirst {
    case Success(x) => x
  } getOrElse (throw original)

但是您原来的递归实现更清晰,更高效。

修改

如果Scala的foldRight具有与Haskell的foldr相同的懒惰属性,则可以使用foldRight来定义:

implicit class GiveStreamAWorkingFoldRight[A](val s: Stream[A]) extends AnyVal {
  def lazyFoldRight[B](z: => B)(f: (A, () => B) => B): B =
    if (s.isEmpty) z else f(s.head, () => s.tail.lazyFoldRight(z)(f))
}

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = 
  zomOldList.toStream.lazyFoldRight(throw original) { (a, b: () => Double) =>
    try {
      a(string)
    } catch {
      case ex: Exception => b()
    }
  }

然而,Scala缺乏真正的尾部调用优化意味着每次调用b都会引入一个新的堆栈帧,可能导致堆栈溢出。

答案 2 :(得分:0)

这是一个使用foldLeft的解决方案。自从我第一次编写一个由tryOldString

调用的泛型函数以来,它很冗长
  def tryOld[In, Error, Out](
    in: In,
    original: Error,
    zomOldList: List[In => Either[Error, Out]]
  ): Either[Error, Out] = {
    val seed: Either[Error, Out] = Left(original)
    zomOldList.foldLeft(seed) {
      case (prev, f) =>
        // stores first match without return
        if (seed != prev) {
          prev
        } else {
          f(in).fold(
            fa =>
              prev,
            fb =>
              Right(fb)
          )
        }
    }
  }

  def tryOutString(string: String, original: Exception, zomOldList: List[String => Double]): Double = {
    val zomFunctions: List[String => Either[Exception, Double]] = zomOldList.map {
      f =>
        s: String =>
          try {
            Right(f(s))
          } catch {
            case e: Exception =>
              Left(e)
          }
    }
    tryOld(string, original, zomFunctions).fold(
      bad => throw original,
      good => good
    )
  }