2017-02-14 128 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]如果您修改代碼,只取最後一個)。最好用方法的返回類型來描述它實際正在做的事情的一部分 - 可能會返回錯誤。