Рекурсивная функция Scala с Будущим типом возвращаемого значения

#scala #concurrency

#scala #параллелизм

Вопрос:

Я пишу рекурсивную функцию повтора в scala, где я хочу знать, была ли ошибка во время выполнения при создании future. Если есть, то повторяется создание экземпляра future.

Представьте, что у меня есть функция запроса к базе данных:

 dbLookup(userId : UserId) : Option[UserName] = ???
  

Повторная попытка будет выглядеть примерно так:

 retry[T](f : () => Future[Option[T]],  
         notifyFailure : (t : Throwable) => Unit,
         n : Int) : Future[Option[T]] = {
  if(n <= 0) { Future{None} }
  else {
    val fut = f()
    if(f.resultsInException) {      //I don't know how to write this
      notifyFailure(f.exception)
      retry(f, notifyFailure, n-1) //try again
    }
    else {
      f.originalValueAsFuture
    }
  }
}
  

Как эта будущая функциональность может быть реализована и обеспечить оптимизацию хвостовой рекурсии?

Эта функция может использоваться для повторной попытки базы данных 10 раз для пользователя, если контекст выполнения продолжает выдавать исключение при попытке создать Future:

 val userNameAfterRetries = 
  retry(() => Future{dbLookup("1234")},
        (t) => system error (s"future creation error : $t"),
        10)
  

Примечание: это вроде как возможно с помощью Future.fallbackTo , но, к сожалению, fallbackTo принимает Future[T] вместо () => Future[T] . Это важно, потому что использование fallbackTo приведет к повторной попытке по крайней мере 1 дополнительный раз, даже если первая попытка была успешной.

Заранее благодарю вас за ваше рассмотрение и ответ.

Комментарии:

1. Можете ли вы использовать «OnFailure» из Future? Может быть, я что-то упускаю?

2. @Jegan onFailure может «зарегистрировать» notifyFailure для запуска в случае возникновения сбоя, но тогда мне нужно вернуть значение повторной попытки…

Ответ №1:

Как насчет этого?

   def retry[T](f: () => Future[Option[T]],
               notifyFailure: Throwable => Unit,
               n: Int)(implicit ec : ExecutionContext): Future[Option[T]] = {
    if (n <= 0) Future.failed(new RuntimeException("Exceeded number of allowed retries"))
    else f().recoverWith { case originalError => notifyFailure(originalError); retry(f, notifyFailure, n - 1) }
  }
  

Обновление по хвостовой рекурсии: Характер Future асинхронный, поэтому, если вы не хотите await получить результат, я не совсем понимаю, возможно ли это сделать @tailrec , потому что вам придется использовать рекурсию при обратном вызове.

Также практическое примечание: если вы знаете, что это всегда ~ 10 попыток, я бы не боялся рекурсии.

Комментарии:

1. ОБНОВЛЕНИЕ: Фактически упрощенное решение до самого крошечного из возможных 🙂

2. Я думаю, вопрос требует рекурсивного решения.

3. На самом деле это так, взгляните на часть «else», она восстанавливает future с помощью рекурсивного вызова метода «retry»

4. На самом деле это не так, попробуйте добавить аннотацию @tailrec к вашему методу. Но меня бы это устроило 🙂 поскольку ‘n’ здесь может быть только 10.

5. Да, вы правы. повторный вызов не находится в конечной позиции. Я могу попытаться придумать хвостовое рекурсивное решение, но, вероятно, позже сегодня 🙂