it-swarm.com.ru

Как в Scala реализовать такой повторный вызов?

Все еще новичок в Scala, и я сейчас ищу способ реализовать на нем следующий код:

@Override
public void store(InputStream source, String destination, long size) {

    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentLength(size);
    final PutObjectRequest request = new PutObjectRequest(
            this.configuration.getBucket(), destination, source, metadata);

    new RetryableService(3) {

        @Override
        public void call() throws Exception {
            getClient().putObject(request);
        }
    };

}

Как лучше всего реализовать ту же функциональность, которую реализует RetryableService, но в Scala?

Он в основном вызывает call метод N раз, если все они терпят неудачу, тогда возникает исключение, если они успешны, он движется дальше. Этот ничего не возвращает, но у меня есть другая версия, которая позволяет возвращать значение (поэтому у меня есть два класса в Java), и я считаю, что я мог бы сделать с одним классом/функцией в Scala.

Есть идеи?

ПРАВКА

Текущая реализация в Java выглядит следующим образом:

public abstract class RetryableService {

private static final JobsLogger log = JobsLogger
        .getLogger(RetryableService.class);

private int times;

public RetryableService() {
    this(3);
}

public RetryableService(int times) {
    this.times = times;
    this.run();
}

private void run() {

    RuntimeException lastExceptionParent = null;

    int x = 0;

    for (; x < this.times; x++) {

        try {
            this.call();
            lastExceptionParent = null;
            break;
        } catch (Exception e) {
            lastExceptionParent = new RuntimeException(e);
            log.errorWithoutNotice( e, "Try %d caused exception %s", x, e.getMessage() );

            try {
                Thread.sleep( 5000 );
            } catch (InterruptedException e1) {
                log.errorWithoutNotice( e1, "Sleep inside try %d caused exception %s", x, e1.getMessage() );
            }

        }

    }

    try {
        this.ensure();
    } catch (Exception e) {
        log.error(e, "Failed while ensure inside RetryableService");
    }

    if ( lastExceptionParent != null ) {
        throw new IllegalStateException( String.format( "Failed on try %d of %s", x, this ), lastExceptionParent);
    }   

}

public void ensure() throws Exception {
    // blank implementation
}

public abstract void call() throws Exception;

}
47
Maurício Linhares

Рекурсия + функции первого класса параметры по имени == потрясающе.

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

Использование это так:

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

Edit: небольшое изменение, вдохновленное ответом @themel . На одну строку кода меньше :-)

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

Правка снова: рекурсия беспокоила меня тем, что она добавила несколько вызовов в трассировку стека. По какой-то причине компилятор не смог оптимизировать хвостовую рекурсию в обработчике catch. Хвостовая рекурсия не в обработчике catch, тем не менее, оптимизирует просто отлично :-)

@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)
  }
}

Редактировать еще раз: Очевидно, я собираюсь сделать это хобби, чтобы продолжать возвращаться и добавлять альтернативы к этому ответу. Вот хвостовая рекурсивная версия, которая немного проще, чем использование Option, но использование return для короткого замыкания функции не является идиоматической 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. Как и мое хобби, я иногда возвращаюсь к этому ответу. Scala 2.10 в представленном виде Try , который обеспечивает чистый способ реализации повторных попыток с хвостовой рекурсией.

// 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
  }
}
155
leedm777

В scalaz.concurrent.Task[T] есть метод: http://docs.typelevel.org/api/scalaz/nightly/#scalaz.concurrent.Task

def retry(delays: Seq[Duration], p: (Throwable) ⇒ Boolean = _.isInstanceOf[Exception]): Task[T]

Используя Task[T], вы можете создать новый Task[T], который будет повторять определенное количество раз, где задержка между попытками определяется параметром delays. например.:

// Task.delay will lazily execute the supplied function when run
val myTask: Task[String] =
  Task.delay(???)

// Retry four times if myTask throws Java.lang.Exception when run
val retryTask: Task[String] =
  myTask.retry(Seq(20.millis, 50.millis, 100.millis, 5.seconds))

// Run the Task on the current thread to get the result
val result: String = retryTask.run
6
Gary Coady

Вот одна из возможных реализаций:

def retry[T](times: Int)(fn: => T) = 
    (1 to times).view flatMap (n => try Some(fn) catch {case e: Exception => None}) headOption

Вы можете использовать это так:

retry(3) {
    getClient.putObject(request)
}

retry также возвращает Some[T], если тело было успешно обработано, и None, если тело генерировало только исключения.


Обновление

Если вы хотите создать последнее исключение, вы можете использовать очень похожий подход, но использовать Either вместо Option:

def retry[T](times: Int)(fn: => T) = {
    val tries = (1 to times).toStream map (n => try Left(fn) catch {case e: Exception => Right(e)}) 

    tries find (_ isLeft) match {
        case Some(Left(result)) => result
        case _ => throw tries.reverse.head.right.get
    }
}

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

5
tenshi

Я бы предложил это - 

def retry[T](n: Int)(code: => T) : T = { 
  var res : Option[T] = None
  var left = n 
  while(!res.isDefined) {
    left = left - 1 
    try { 
      res = Some(code) 
    } catch { 
      case t: Throwable if left > 0 => 
    }
  } 
  res.get
} 

Оно делает: 

scala> retry(3) { println("foo"); }
foo

scala> retry(4) { throw new RuntimeException("nope"); }
Java.lang.RuntimeException: nope
        at $anonfun$1.apply(<console>:7)
        at $anonfun$1.apply(<console>:7)
        at .retry(<console>:11)
        at .<init>(<console>:7)
        at .<clinit>(<console>)
        at RequestResult$.<init>(<console>:9)
        at RequestResult$.<clinit>(<console>)
        at RequestResult$scala_repl_result(<console>)
        at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:39)
        at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
        at Java.lang.reflect.Method.invoke(Method.Java:597)
        at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter.scala:988)
        at scala.tools.nsc.Interpreter$Request$$anonfun$loadAndRun$1$$anonfun$apply$17.apply(Interpreter....
scala> var i = 0 ;
i: Int = 0

scala> retry(3) { i = i + 1; if(i < 3) throw new RuntimeException("meh");}

scala> i
res3: Int = 3

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

4
themel

Вы можете выразить идею в функциональном стиле, используя 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);
  }

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

Этот подход дает вам дополнительное преимущество, заключающееся в том, что вы можете параметризовать контейнер перехвата, поэтому вы можете только повторить определенное подмножество исключений, добавить финализаторы и т.д. Таким образом, окончательная версия retry может выглядеть следующим образом:

/** 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);
  }

С этим вы можете делать сложные вещи, такие как:

retry(Exception.allCatch andFinally { print("Finished.") }, 3) {
  // your scode
}
3
Petr Pudlák

Существует существующая библиотека, которая может помочь с этим, называемая retry , и есть также библиотека Java, называемая guava-retry .

Вот несколько примеров использования retry :

// retry 4 times
val future = retry.Directly(4) { () => doSomething }

// retry 3 times pausing 30 seconds in between attempts
val future = retry.Pause(3, 30.seconds) { () => doSomething }

// retry 4 times with a delay of 1 second which will be multipled
// by 2 on every attempt
val future = retry.Backoff(4, 1.second) { () => doSomething }
3
Hosam Aly

Мне нравится принятое решение, но предлагаю проверить исключение 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
  }
}

Вы не хотите повторять исключение потока управления, и обычно не для прерываний потока ...

2
srnm

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

  /**
   * Attempt 'fn' up to 'attempts' times, retrying only if 'forExceptions' returns true for retry-able exceptions.
   */
  def retry[T](attempts: Int, forExceptions: (Throwable) => Boolean)(fn: => T): T =
  {
    // toStream creates a lazily evaluated list, which we map to a try/catch block resulting in an Either
    val tries = (1 to attempts).toStream map
      {
        n =>
          try
            Left(fn)
          catch
            {
              case e if forExceptions(e) => Right(e)
            }
      }

    // find the first 'Either' where left is defined and return that, or if not found, return last
    // exception thrown (stored as 'right').  The cool thing is that because of lazy evaluation, 'fn' is only
    // evaluated until it success (e.g., until Left is found)
    tries find (_ isLeft) match
    {
      case Some(Left(result)) => result
      case _ => throw tries.reverse.head.right.get
    }

  }

Вы можете позвонить двумя способами:

val result = retry(4, _.isInstanceOf[SomeBadException])
{
   boom.doit()
}

или с частичными функциями (также показывающими версию, в которой не важно возвращаемое значение)

    def pf: PartialFunction[Throwable, Boolean] =
    {
      case x: SomeOtherException => true
      case _ => false
    }

   retry(4, pf)
   {
      boom.doit()
   }
1
Doug Donohoe

Если вы хотите контролировать, какие исключения вы повторяете, вы можете использовать методы в scala.util.control.Exception:

import Java.io._
import scala.util.control.Exception._

def ioretry[T](n: Int)(t: => T) = (
  Iterator.fill(n){ failing[T](classOf[IOException]){ Option(t) } } ++
  Iterator(Some(t))
).dropWhile(_.isEmpty).next.get

(Как написано, он также будет повторять попытки на нуль; это часть Option(t). Если вы хотите, чтобы возвращались нули, вместо этого используйте Some(t) внутри заполнения итератора.)

Давайте попробуем это с

class IoEx(var n: Int) {
  def get = if (n>0) { n -= 1; throw new IOException } else 5
}
val ix = new IoEx(3)

Это работает?

scala> ioretry(4) { ix.get }
res0: Int = 5

scala> ix.n = 3

scala> ioretry(2) { ix.get }
Java.io.IOException
    at IoEx.get(<console>:20)
    ...

scala> ioretry(4) { throw new Exception }
Java.lang.Exception
    at $anonfun$1.apply(<console>:21)
    ...

Выглядит хорошо!

1
Rex Kerr

Похоже, этот проект предоставляет некоторые реализации Nice для различных механизмов повторения 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
hipjim

Повторно используемый объект/метод с паузой между попытками:

Retry(3, 2 seconds) { /* some code */ }

Код:

object Retry {
  def apply[A](times: Int, pause: Duration)(code: ⇒ A): A = {
    var result: Option[A] = None
    var remaining = times
    while (remaining > 0) {
      remaining -= 1
      try {
        result = Some(code)
        remaining = 0
      } catch {
        case _ if remaining > 0 ⇒ Thread.sleep(pause.toMillis)
      }
    }
    result.get
  }
}
0
Devis Lucato

Это решение не оптимизировано компилятором для хвостовой рекурсии по какой-то причине (кто знает почему?), Но в случае редких повторных попыток будет вариант:

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

Использование:

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

Конец ответа. Хватит читать здесь


Версия с результатом как попытка:

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

Использование:

// 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()
}

Версия с функцией возврата Try

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

Использование:

// 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
Sergii Pogodin
//Here is one using Play framework

def retry[T](times:Int)(block: => Future[T])(implicit ctx: ExecutionContext):Future[T] = {

type V = Either[Throwable,T]
val i:Iterator[Future[Option[V]]] = 
  Iterator.continually(block.map(t => Right(t)).recover { case e => Left(e) }.map(t => Some(t)))
def _retry:Iteratee[V,V] = {
    def step(ctr:Int)(i:Input[V]):Iteratee[V,V] = i match {
        case Input.El(e) if (e.isRight) => Done(e,Input.EOF)
        case _ if (ctr < times) => Cont[V,V](i => step(ctr + 1)(i))
        case Input.El(e) => Done(e,Input.EOF)
    }
    Cont[V,V](i => step(0)(i))
}
Enumerator.generateM(i.next).run(_retry).flatMap { _ match {
  case Right(t) => future(t)
  case Left(e) => Future.failed(e)
}}
}
0
Santhosh Sath