What's the Scala way to implement a retry-able call like this one?

前端 未结 14 1701
甜味超标
甜味超标 2020-11-29 16:08

Still the newbie in Scala and I\'m now looking for a way to implement the following code on it:

@Override
public void store(InputStream source, String destin         


        
相关标签:
14条回答
  • 2020-11-29 16:53

    I like the accepted solution, but suggest checking the exception is NonFatal:

    // Returning T, throwing the exception on failure
    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): T = {
      Try { fn } match {
        case Success(x) => x
        case _ if n > 1 && NonFatal(e) => retry(n - 1)(fn)
        case Failure(e) => throw e
      }
    }
    

    You don't want to retry a control flow exception, and usually not for thread interrupts...

    0 讨论(0)
  • 2020-11-29 16:55

    This project seems to provide some nice implementations for different retry mechanisms https://github.com/hipjim/scala-retry

    // 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 讨论(0)
  • 2020-11-29 16:56

    This solution is not optimized by compiler to tail recursion for some reason (who knows why?), but in case of rare retries would be an option:

    def retry[T](n: Int)(f: => T): T = {
      Try { f } recover {
        case _ if n > 1 => retry(n - 1)(f)
      } get
    }
    

    Usage:

    val words: String = retry(3) {
      whatDoesTheFoxSay()
    }
    

    End of the answer. Stop reading here


    Version with result as a Try:

    def reTry[T](n: Int)(f: => T): Try[T] = {
      Try { f } recoverWith {
        case _ if n > 1 => reTry(n - 1)(f)
      }
    }
    

    Usage:

    // previous usage section will be identical to:
    val words: String = reTry(3) {
      whatDoesTheFoxSay()
    } get
    
    // Try as a result:
    val words: Try[String] = reTry(3) {
      whatDoesTheFoxSay()
    }
    

    Version with a function returning Try

    def retry[T](n: Int)(f: => Try[T]): Try[T] = {
      f recoverWith {
        case _ if n > 1 => reTry(n - 1)(f)
      }
    }
    

    Usage:

    // the first usage section will be identical to:
    val words: String = retry(3) {
      Try(whatDoesTheFoxSay())
    } get
    
    // if your function returns Try:
    def tryAskingFox(): Try = Failure(new IllegalStateException)
    
    val words: Try[String] = retry(3) {
        tryAskingFox()
    }
    
    0 讨论(0)
  • 2020-11-29 17:00

    Recursion + first class functions by-name parameters == awesome.

    def retry[T](n: Int)(fn: => T): T = {
      try {
        fn
      } catch {
        case e =>
          if (n > 1) retry(n - 1)(fn)
          else throw e
      }
    }
    

    Usage is like this:

    retry(3) {
      // insert code that may fail here
    }
    

    Edit: slight variation inspired by @themel's answer. One fewer line of code :-)

    def retry[T](n: Int)(fn: => T): T = {
      try {
        fn
      } catch {
        case e if n > 1 =>
          retry(n - 1)(fn)
      }
    }
    

    Edit Again: The recursion bothered me in that it added several calls to the stack trace. For some reason, the compiler couldn't optimize tail recursion in the catch handler. Tail recursion not in the catch handler, though, optimizes just fine :-)

    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): T = {
      val r = try { Some(fn) } catch { case e: Exception if n > 1 => None }
      r match {
        case Some(x) => x
        case None => retry(n - 1)(fn)
      }
    }
    

    Edit yet again: Apparently I'm going to make it a hobby to keep coming back and adding alternatives to this answer. Here's a tail-recursive version that's a bit more straightforward than using Option, but using return to short-circuit a function isn't idiomatic Scala.

    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): T = {
      try {
        return fn
      } catch {
        case e if n > 1 => // ignore
      }
      retry(n - 1)(fn)
    }
    

    Scala 2.10 update. As is my hobby, I revisit this answer occasionally. Scala 2.10 as introduced Try, which provides a clean way of implementing retry in a tail-recursive way.

    // Returning T, throwing the exception on failure
    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): T = {
      util.Try { fn } match {
        case util.Success(x) => x
        case _ if n > 1 => retry(n - 1)(fn)
        case util.Failure(e) => throw e
      }
    }
    
    // Returning a Try[T] wrapper
    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): util.Try[T] = {
      util.Try { fn } match {
        case util.Failure(_) if n > 1 => retry(n - 1)(fn)
        case fn => fn
      }
    }
    
    0 讨论(0)
  • 2020-11-29 17:01

    Minor improvement to printout attempt x of N

    // Returning T, throwing the exception on failure
          @annotation.tailrec
          final def retry[T](n: Int, name: String ="", attemptCount:Int = 1)(fn: => T): T = {
            logger.info(s"retry count: attempt $attemptCount of $n ....... function: $name")
            try {
              val result = fn
              logger.info(s"Succeeded: attempt $attemptCount of $n ....... function: $name")
              result
            } catch {
              case e: Throwable =>
                if (n < attemptCount) { Thread.sleep(5000 * attemptCount); retry(n, name, attemptCount+1)(fn) }
                else throw e 
            }
          }
    
    0 讨论(0)
  • 2020-11-29 17:02

    You can express the idea in functional style using scala.util.control.Exception:

    @annotation.tailrec
    def retry[T](n: Int)(fn: => T): T =
      Exception.allCatch.either(fn) match {
        case Right(v)             => v;
        case Left(e) if (n <= 1)  => throw e;
        case _                    => retry(n - 1)(fn);
      }
    

    As we can see, tail recursion can be used here.

    This approach gives you the additional benefit that you can parametrize the catch container, so you can only retry a certain subset of exceptions, add finalizers etc. So the final version of retry might look like:

    /** Retry on any exception, no finalizers. */
    def retry[T](n: Int)(fn: => T): T =
      retry(Exception.allCatch[T], n)(fn);
    
    /** Parametrized retry. */
    @annotation.tailrec
    def retry[T](theCatch: Exception.Catch[T], n: Int)(fn: => T): T =
      theCatch.either(fn) match {
        case Right(v)             => v;
        case Left(e) if (n <= 1)  => throw e;
        case _                    => retry(theCatch, n - 1)(fn);
      }
    

    With this, you can do complex stuff like:

    retry(Exception.allCatch andFinally { print("Finished.") }, 3) {
      // your scode
    }
    
    0 讨论(0)
提交回复
热议问题