обертывание общей функции обратного вызова с разными подписями

Я пытаюсь изучить Scala, и пока он кажется очень мощным языком, но некоторые вещи кажутся труднодостижимыми, может быть, это просто моя кривая обучения. Я искал в Интернете несколько дней, но не смог найти хорошего решения, чтобы делать то, что я хочу делать.

У меня есть несколько методов (с разными сигнатурами, включая тип возвращаемого значения), которые я хочу обернуть в логике повтора.

Я хочу продолжать вызывать метод заранее определенное количество раз, пока метод не завершится успешно.

Вот пример:

def downloadLocal(memory: Boolean, userName: Name, version: Int): Int

def downloadRemote(
  memory: Boolean, userName: Name, masterNodeId: String, masterNodeId: String
): Pair(Int, Int)

Я хочу заключить эти два метода в логику повтора. Вот моя попытка логики повтора:

trait WithRetry{
  def withRetry(retry :Int){
    try{
      callBack
    }catch{
      case exception:Throwable =>
        if(retry>0){
          logger.warn(exec+" method failed with an exception. retry number is:" + retry);
          logger.warn(exception);
          withRetry(retry-1)
        }
        else{
          throw exception
        }
    }
  }
  def callBack():Any
}

У меня проблема в том, что я не могу найти чистый способ обернуть мои методы (downloadRemote и downloadLocal) внутри логики повтора.

Есть предложения / мысли?


person ajaymysore    schedule 27.07.2015    source источник


Ответы (1)


Вы можете просто определить общую функцию:

import scala.util.{Try, Success, Failure}

def retry[T](times: Int)(block: => T): T = Try(block) match {
  case Success(result) => result
  case Failure(e) =>
    if (times > 0) {
      logger.warn(s"method failed with an exception, retry #$times")
      logger.warn(exception)
      retry(times - 1)(block)
    }
    else throw e
}

Это хвостовая рекурсивная функция, поэтому она будет столь же эффективна, как вызов функции в цикле for в Java.

Однако более идиоматическим Scala было бы возвращать Try:

def retry2[T](times: Int)(block: => T): Try[T] = 
  Try(block) match {
    case Failure(e) if (times > 0) =>
      logger.warn(s"method failed with an exception, retry #$times")
      logger.warn(exception)
      retry2(times - 1)(block)
    case other => other
  }

Обе версии могут использоваться как:

def failRandomly: Int = 
  if (scala.util.Random.nextInt < 0.80) throw new Exception("boom")
  else 1

scala> retry(2)(failRandomly)
res0: Int = 1
scala> retry(2)(failRandomly)
java.lang.Exception: boom  // ...

scala> retry2(2)(failRandomly)
res1: scala.util.Try[Int] = Success(1)
scala> retry2(2)(failRandomly)
res2: scala.util.Try[Int] = Failure(java.lang.Exception: boom)
person Peter Neyens    schedule 27.07.2015
comment
Большое спасибо за ваш ответ. Каков предлагаемый подход к тому, чтобы сделать его переносимым, чтобы другие методы могли его использовать? Могу ли я просто обернуть его в класс (как показано ниже) и сделать метод статическим или есть способ лучше? жирным шрифтом открытый класс RetryUtil {def retry [T] (times: Int) (block: = ›T): T = Try (block) match {case Success (result) =› result case Failure (e ) = ›If (раз› 0) {logger.warn (smethod не удалось с исключением, повторить # $ retry) logger.warn (exception) retry (times - 1) (block)} else throw e}} жирным шрифтом - person ajaymysore; 28.07.2015
comment
Вы можете использовать его с любой функцией, например: retry2(5)(downloadLocal(true, "username", 2)) - person Peter Neyens; 28.07.2015
comment
В Scala у нас нет статических методов, мы бы создали метод в объекте. См. Разница между объектом и классом в Scala - person Peter Neyens; 28.07.2015