根据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)
}
}
}
答案 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
的懒惰,并根据collectFirst
和Try
来实施:
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
)
}