10
votes

Voie fonctionnelle déjà existante pour une nouvelle tentative jusqu'à Scala?

Y a-t-il une façon fonctionnelle / scala d'appeler une fonction à plusieurs reprises jusqu'à ce qu'elle réussisse, tout en réagissant aux tentatives infructueuses?

Permettez-moi d'illustrer avec un exemple. Supposons que je souhaite lire un entier de la norme-IN et réessayer si l'utilisateur n'entraînait pas en integile.

donné cette fonction: xxx

Et ces fonctions anonymes: xxx

Je les utiliserais comme ceci: xxx

Mes questions sont:

  • fait quelque chose comme retry_until_right existe déjà dans SCALA?
  • pourrait-il être résolu avec les installations existantes? (Streams, Itérateurs, Monads, etc.)
  • L'une des bibliothèques FP (Scalaz?) offre quelque chose comme ça?
  • Puis-je faire quelque chose de mieux / plus idiomatique? (*)

    merci!

    *) à part le serpent_case. Je vraiment comme ça.


2 commentaires

duplicaté possible de Quel est le scala façon de mettre en œuvre un appel à nouveau capable comme celui-ci?


Je ne pense pas que ma question soit une duplicate de celui-là. Celui-ci est spécifique à l'échec par exception et la quantité de tentatives est numérotée. Ma question est plus abstraite. L'autre question pourrait être un cas particulier de mien. Cette question est également "quelle est la meilleure façon de le mettre en œuvre?", Est "Dois-je la mettre en œuvre?" (Ou existe-t-il déjà?)


5 Réponses :


3
votes

C'était ma première implémentation récursive de la queue: xxx pré>

mais désirant réutiliser les bibliothèques existantes, j'ai ensuite changé sur cette approche à l'aide d'itérateurs: P>

val num = Iterator.continually(ask_for_int()).onLeft(handle_not_int).firstRight

println("Thanks! You entered: " + num)


3 commentaires

Le Toseq ne produira-t-il pas une collection infinie? Peut-être que vous voulez un bufferéditerator pour que vous puissiez avoir la tête?


@Phaises - Je pense que ça ne le fait pas. Tolist semble forcer la collection, pas Toseq. Mais je pourrais me tromper. Au moins je l'ai essayé et ça marche.


Notez également qu'il ne peut pas réussir à produire une collection infinie, car cela dépend de l'entrée de Ask_for_int (). Cela doit attendre. Cela entraînerait une boucle infinie si ask_for_int () n'a jamais retourné un int. Mais c'est l'idée de toute façon.



1
votes
def retry[L, R](f: => Either[L, R])(handler: L => Any): R = {
    val e = f
    e.fold(l => { handler(l); retry(f)(handler) }, identity)
}

2 commentaires

Utilisation très intéressante du pliage et en particulier identité . Était nouveau pour moi.


La fonction d'identité est une chose très naturelle à utiliser lors de la pliage avec des fonctions @sebastiann. . De la même manière que 0 est la graine naturelle lors du pliage d'une somme et 1 lors du pliage d'un produit, l'identité est la graine naturelle où un pli construirait une fonction cumulative. Si la collection est vide, vous recevez une fonction qui renvoie simplement son argument. Sinon, vous recevez une fonction itérative qui transforme l'argument (avec une identité étant une étape initiale ou finale inoffensive). Utile avec les collections, l'option, soit des autres.



6
votes

Je pense que le essayer monad avec le itérateur.Continalement est adapté à ce problème général. Bien sûr, cette réponse pourrait être adoptée pour utiliser si vous êtes tellement enclin: xxx

alors vous pouvez faire: xxx

ou vous pouvez même masquer le essayer une partie de la mise en œuvre et donner oncwrong une valeur par défaut et en faire un second paramètre au lieu d'une fonction curry: xxx

alors vous pouvez simplement: xxx

ou xxx / p>


1 commentaires

Je vois que votre approche est similaire à la mienne (continuellement / platmap / toseq / tête). Heureux de voir que j'étais sur la bonne voie. Il semble donc que cela ne soit pas intégré. Peut-être que Scalaz, informe ou certaines d'entre elles ont quelque chose comme ça ...



5
votes

Voici une solution alternative utilisant SCALAZ.CONCURRENT.TASK:

def retry[A](f: Task[A])(onError: PartialFunction[Throwable, Task[_]]): Task[A] =
  f handleWith (onError andThen (_.flatMap(_ => retry(f)(onError))))

val rawReadInt: Task[Int] = Task.delay(scala.io.StdIn.readLine().trim().toInt)

val readInt: Task[Int] = retry(rawReadInt) {
  case e: java.lang.NumberFormatException => Task.delay(println("Failure!"))
}


3 commentaires

Désolé ... Newbie ici ... Qu'est-ce qu'un "trampoline" dans ce contexte?


Le trampoline est une stratégie pour éliminer les débordements de pile; Il représente le calcul hiérarchique sous forme de structure de données au lieu d'un ensemble de cadres de pile. Le "trampoline" est alors un cadre d'exécution qui peut traiter cette structure de données étape par étape jusqu'à ce que le résultat final soit évalué. De l'scaladoc pour ScalAck pour Scalaz.ConCurrent.Future: "L'avenir est un calcul trampoliné produisant un A qui peut inclure des marches asynchrones. Comme le trampoline, des expressions monadiques arbitraires impliquant une carte et une colonne graphique sont garanties pour utiliser un espace de pile constant." Voir aussi: "Programmation fonctionnelle à Scala", ch. 13


Peut-être une meilleure référence est le papier, "Stackless Scala avec des monades libres", de Rúnar Óli Bjarnason: blog.higher-order.com/assets/trampolines.pdf



0
votes

Une mise en œuvre d'une nouvelle monade peut être trouvée ici: https://github.com/hipjim/scala-retry Il a diverses stratégies de réessayes.

// define the retry strategy
implicit val retryStrategy =
    RetryStrategy.fixedBackOff(retryDuration = 1.seconds, maxAttempts = 2)

// pattern match the result
val r = Retry(1 / 1) match {
    case Success(x) => x
    case Failure(t) => log("I got 99 problems but you won't be one", t)
}


0 commentaires