繁体   English   中英

如何将递归转换为折叠

[英]How to convert recursion to fold

根据Erik Meijer的说法,作为函数式程序员,我们都知道,应该使用fold而不是递归。 如何转换以下内容以使用折叠? 我可以看到带有return的一种方法,但在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)
      }
  }
}

您可以利用foldRight来实现这一点, 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来避免捕获我们不应该捕获的异常。 您可以通过不直接使用异常来以更优雅的方式编写代码。

您不能一implement而就。 fold遍历集合的每个元素,而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)

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

编辑:

如果斯卡拉有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每次调用都会引入一个新的堆栈帧,从而可能导致堆栈溢出。

这是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
    )
  }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM