2017-02-14 103 views
1

我想一个函数f适用于List的每个元素,并在第一个错误不会停止,但是只能扔过去的错误(如果有的话):斯卡拉尾递归从finally块

@annotation.tailrec 
def tryAll[A](xs: List[A])(f: A => Unit): Unit = { 
    xs match { 
    case x :: xt => 
     try { 
     f(x) 
     } finally { 
     tryAll(xt)(f) 
     } 
    case _ => 
    } 
} 

但是,在上面的代码不会编译 - 它抱怨这个函数不是尾递归的。为什么不?

+1

功能是不是尾递归,因为在案件的异常被抛出,'finally'块未得到执行的最后一个代码。 –

+0

@HristoIliev:我明白了 - 我怎样才能以高效和惯用的方式来写这些呢? – pathikrit

+0

我相信习惯的方法是使用'scala.util.Try'来包装函数调用,但我无法为你提供一个示例代码。 –

回答

1

该解决方案遍历所有元素,并产生(抛出)的最后一个错误,如果任何:

def tryAll[A](xs: List[A])(f: A => Unit): Unit = { 
    val res = xs.foldLeft(Option.empty[Throwable]) { 
    case (maybeThrowable, a) => 
     Try(f(a)) match { 
     case Success(_) => maybeThrowable 
     case Failure(e) => Option(e) 
     } 
    } 

    res.foreach(throwable => throw throwable) 
} 
-1

不知道该方法的意图,但你可以这样的事情:

final def tryAll[A](xs: List[A])(f: A => Unit): Unit = { 
     xs match { 
     case x :: xt => 
      try { 
      f(x) 
      } catch { 
      case e => tryAll(xt)(f) 
      } 
     case _ => //do something else 
     } 
    } 
+0

这将导致出现StackOverflow如果列表中包含了> 100万件.. – pathikrit

-1

我知道这种方式使用@ annotation.tailrec

从这:

def fac(n:Int):Int = if (n<=1) 1 else n*fac(n-1) 

你应该有这样的:

@scala.annotation.tailrec 
def facIter(f:Int, n:Int):Int = if (n<2) f else facIter(n*f, n-1) 
def fac(n:Int) = facIter(1,n) 
+0

你必须在积累的F值,所以当你到达基地时,返回。它对你的方法没有意义:/ – mychemicalro

0

正如@HristoIliev提到的,你的方法不能是尾递归,因为finally呼叫不保证是尾调用。这意味着以这种方式使用try的任何方法都不是尾递归的。另请参阅this answer

再次调用该方法是反复尝试的东西,直到成功,因为在每一个阶段,它抛出你大概不处理异常奇怪的是。相反,我认为在Try中使用功能性方法,从视图中取出失败,直到操作成功。这种方法的唯一缺点是它不会抛出任何异常情况来处理(这也可能是一个优点!)。

def tryAll[A](xs: List[A])(f: A => Unit): Unit = 
    xs.view.map(x => Try(f(x))).takeWhile(_.isFailure).force 


scala> val list = List(0, 0, 0, 4, 5, 0) 

scala> tryAll(list)(a => println(10/a)) 
2 

如果你真的要处理的异常(或刚刚过去除外),你可以在tryAll返回类型更改为List[Try[Unit]](或者干脆Try[Unit]如果您修改代码,只取最后一个)。最好用方法的返回类型来描述它实际正在做的事情的一部分 - 可能会返回错误。