このような再試行可能な呼び出しを Scala で実装する方法は何ですか? 質問する

このような再試行可能な呼び出しを Scala で実装する方法は何ですか? 質問する

私はまだ 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 で実装する最良の方法は何でしょうか?

それは基本的に電話メソッドを N 回実行し、すべて失敗した場合は例外が発生し、成功した場合は次に進みます。これは何も返しませんが、値を返すことができる別のバージョンがあります (つまり、Java で 2 つのクラスがあります)。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;

}

ベストアンサー1

再帰 + ファーストクラス関数 の名前によるパラメータ == 素晴らしい。

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
}

編集: インスピレーションを受けた若干のバリエーション@セメルの答えです。コードが 1 行減りました :-)

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が導入されました試すは、末尾再帰的な方法で再試行を実装するクリーンな方法を提供します。

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

おすすめ記事