2017-02-14 2 views
1

ich eine Funktion f zu jedem Element einer List anwenden möchten, und nicht beim ersten Fehler zu stoppen, aber den letzten Fehler (falls vorhanden) werfen nur:Scala Endrekursion von finally-Block

@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 _ => 
    } 
} 

Aber die Der obige Code kompiliert nicht - er beschwert sich, dass diese Funktion nicht tail rekursiv ist. Warum nicht?

+1

Die Funktion ist nicht tail rekursiv, da in dem Fall, dass eine Ausnahme ausgelöst wird, der 'finally' Block nicht der letzte Code ist, der ausgeführt wird. –

+0

@HristoIliev: Ich sehe - wie kann ich das dann effizient und idiomatisch schreiben? – pathikrit

+0

Ich glaube, der idiomatische Weg wäre, 'scala.util.Try' zu verwenden, um die Funktionsaufrufe einzubinden, aber ich kann Ihnen keinen Beispielcode liefern. –

Antwort

1

Diese Lösung iteriert über alle Elemente und produziert (wirft) den letzten Fehler, wenn eine:

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

die Absicht des Verfahrens nicht sicher, aber man kann so etwas:

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

verursachen, was zu einem StackOverflow führen würde, wenn die Liste> 1M Items enthält. – pathikrit

-1

ich kenne diese Art und Weise @ annotation.tailrec

Daraus zu verwenden:

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

Sie sollten dies haben:

@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

Sie müssen Werte in f akkumulieren, also wenn Sie den Basisfall erreichen, geben Sie ihn zurück. Es macht keinen Sinn für Ihre Methode: / – mychemicalro

0

Wie @HristoIliev erwähnt, Ihre Methode nicht Schwanz rekursiv, weil der finally Aufruf der Endaufruf sein ist nicht gewährleistet werden kann. Dies bedeutet, dass jede Methode, die try auf diese Weise verwendet, nicht tail rekursiv ist. Siehe auch this answer, auch.

Die Methode erneut aufrufen ist eine seltsame Art, etwas wiederholt zu versuchen, bis es erfolgreich ist, weil es in jedem Stadium eine Ausnahme auslöst, die Sie vermutlich nicht verarbeiten. Stattdessen würde ich argumentieren, einen funktionalen Ansatz mit Try zu verwenden und Fehler aus einer Sicht zu nehmen, bis der Vorgang erfolgreich ist. Der einzige Nachteil dieses Ansatzes ist, dass er keine Ausnahmen für Sie bereithält (was auch ein Vorteil sein kann!).

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 

Wenn Sie wirklich die Ausnahmen behandeln wollen (oder nur die letzte Ausnahme), können Sie den Rückgabetyp von tryAll zu List[Try[Unit]] ändern (oder einfach Try[Unit] wenn Sie den Code ändern, um nur die letzten nehmen). Es ist besser, wenn der Rückgabetyp der Methode einen Teil dessen beschreibt, was er tatsächlich tut - möglicherweise Fehler zurückgibt.