实现像这样的可重试调用的 Scala 方法是什么? [英] What's the Scala way to implement a retry-able call like this one?

查看:38
本文介绍了实现像这样的可重试调用的 Scala 方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

仍然是 Scala 的新手,我现在正在寻找一种在其上实现以下代码的方法:

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 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 中实现的相同功能的最佳方法是什么?

What would be the best way to implement the same funcionality that RetryableService implements but in Scala?

它基本上调用 call 方法 N 次,如果它们都失败则引发异常,如果它们成功则继续.这个不返回任何东西,但是我有另一个版本允许返回一个值(所以,我在 Java 中有两个类),我相信我可以在 Scala 中使用单个类/函数.

It basically calls the call method N times, if all of them fail the exception is then raised, if they succeed it moves on. This one does not return anything but then I have another version that allows for returning a value (so, i have two classes in Java) and I believe I could do with a single class/function in Scala.

有什么想法吗?

编辑

目前在java中的实现如下:

Current implementation in java is as follows:

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;

}

推荐答案

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

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

用法是这样的:

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

编辑:受 @themel 的回答启发而略有变化.少一行代码:-)

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

再次编辑:递归让我感到困扰,因为它向堆栈跟踪添加了几次调用.出于某种原因,编译器无法优化 catch 处理程序中的尾递归.但是,不在 catch 处理程序中的尾递归优化得很好:-)

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

再次编辑:显然,我将把不断回来并为此答案添加替代方案成为一种爱好.这是一个尾递归版本,它比使用 Option 更直接一些,但是使用 return 来短路函数并不是 Scala 惯用的.

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 更新.作为我的爱好,我偶尔会重温这个答案.Scala 2.10 介绍 Try,它提供一种以尾递归方式实现重试的简洁方法.

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

这篇关于实现像这样的可重试调用的 Scala 方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆