2017-02-14 3 views
1

Je veux appliquer une fonction f à chaque élément d'un List et non arrêter à la première erreur, mais lancer la dernière erreur (le cas échéant) seulement:récursion queue Scala de bloc 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 _ => 
    } 
} 

Mais, la le code ci-dessus ne compile pas - il se plaint que cette fonction n'est pas récursive en queue. Pourquoi pas?

+1

La fonction n'est pas récursive car dans le cas où une exception est levée, le bloc 'finally' n'est pas le dernier code à exécuter. –

+0

@HristoIliev: Je vois - comment puis-je écrire de manière efficace et idiomatique? – pathikrit

+0

Je crois que la façon idiomatique serait d'utiliser 'scala.util.Try' pour envelopper les invocations de fonction, mais je suis incapable de vous fournir un exemple de code. –

Répondre

1

Cette solution itère sur tous les éléments et produit (lancers) la dernière erreur le cas échéant:

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

Je ne sais pas l'intention de la méthode, mais vous pouvez quelque chose comme ça:

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

cela causerait un StackOverflow si la liste contient> 1M éléments. – pathikrit

-1

Je sais que cette façon d'utiliser @ annotation.tailrec

de ceci:

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

Vous devez avoir ceci:

@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

Vous devez accumuler des valeurs dans f, donc lorsque vous atteignez le cas de base, renvoyez-le. Cela n'a aucun sens pour votre méthode: / – mychemicalro

0

Comme mentionné par @HristoIliev, votre méthode ne peut pas être récursive queue parce que l'appel finally n'est pas garanti l'appel de la queue. Cela signifie que toute méthode utilisant try de cette manière ne sera pas récursive. Voir this answer, aussi.

Appeler la méthode à nouveau est une façon bizarre d'essayer quelque chose de façon répétée jusqu'à ce qu'elle réussisse, car à chaque étape elle lance une exception que vous ne manipulez probablement pas. Au lieu de cela, je discuterais en utilisant une approche fonctionnelle avec Try, en prenant des échecs d'une vue jusqu'à ce que l'opération réussisse. Le seul désavantage de cette approche est qu'elle ne vous dérange pas en cours de route (ce qui peut aussi être un avantage!).

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 

Si vous voulez vraiment gérer les exceptions (ou tout simplement la dernière exception), vous pouvez changer (si vous modifiez le code ou tout simplement Try[Unit] à ne prendre la dernière), le type de retour de tryAll à List[Try[Unit]]. Il est préférable que le type de retour de la méthode décrive une partie de ce qu'elle est réellement en train de faire - renvoyant potentiellement des erreurs.