用期货满足抽象特征要求

时间:2013-07-15 21:34:04

标签: scala akka future

我有一个抽象的特性,其中有一些难以计算的要求,然后是这些计算结果的一些函数。我想保持这个特性简单,以便于理解和测试。

trait Calculator {
  def hardToCalculate1: Int
  def hardToCalculate2: Int
  def hardToCalculate3: Int

  def result1 = hardToCalculate1 + hardToCalculate2
  def result2 = hardToCalculate2 + hardToCalculate3
  def result3 = hardToCalculate1 + hardToCalculate3
}

当我实例化Calculator时,我将使用Futures来计算hardToCalculate个值。让我们说它们看起来像这样:

def f1 = future {
    println("calculating 1")
    1
}
def f2 = future {
    println("calculating 2")
    2
}
def f3 = future {
    println("calculating 3")
    3
}

所以,我可以像这样构建一个Future[Calculator]

val myCalc = for {
  m1 <- f1
  m2 <- f2
  m3 <- f3
} yield new Calculator {
  lazy val hardToCalculate1 = m1
  lazy val hardToCalculate2 = m2
  lazy val hardToCalculate3 = m3
}

然后,我可能会像这样使用myCalc

myCalc onComplete {
  case Success(calc) => println("Result: " + calc.result1)
}

但是当我这样做时,我明白了:

calculating 1
calculating 2
calculating 3
Result: 3

如果我正在进行的计算实际需要它们,我只想执行那些期货。即使我使用hardToCalculate声明lazy val s,但在执行Future[Calculator].onComplete时会计算所有三个。

这样做的一种方法是:

val calculator = new Calculator {
    lazy val hardToCalculate1 = Await.result(f1, 10 seconds)
    lazy val hardToCalculate2 = Await.result(f2, 10 seconds)
    lazy val hardToCalculate3 = Await.result(f3, 10 seconds)
}
println("result: " + calculator.result1)

这产生了我想要的东西:

calculating 1
calculating 2
result: 3

但现在我已经完成了所有Await阻止。我真正想要的是Future[Calculator],它将以懒惰的方式执行期货。如果不将Futures引入我的Calculator特质,这可能吗?关于如何获得我在此之后的任何其他建议?

A gist with all of the above code is here.

2 个答案:

答案 0 :(得分:2)

如果您创建Future(使用scala.concurrent.future),无论您做什么,都会计算 。所以你需要一个完全不同的策略。

此外,您的界面甚至无法远程识别您将实际使用的数据。 myCalc的计算如何知道onComplete中您只使用result1

你可以:

  • 仅使用lazy vals:

    val calculator = new Calculator {
      lazy val hardToCalculate1 = {
        println("calculating 1")
        1
      }
      // ...
    }
    

    亲:简单的 骗局:不同步

  • 封装Future以允许请求计算:

    class ReqFuture[T](body: () => T) {
      lazy val fut = future { body() }
    }
    

    但是现在你仍然遇到myCalc将要求并等待所有这些问题的问题。因此,您必须在ReqFutures中介绍Calculator

    trait Calculator {
        def hardToCalculate1: ReqFuture[Int]
        // ...
        def result1 = for {
          h1 <- hardToCalculate1.fut
          h2 <- hardToCalculate2.fut
        } yield h1 + h2
    }
    

    专业:当你致电result1时,只计算你需要的东西(但仍然只计算一次) Con:result1现在是Future[Int]。因此Futures完全渗透了您的Calculator

如果你不能影响Calculator(我怀疑的话)并且无法改变result1,2,3的代码,那么我很遗憾,你无法做任何事情来使执行变得懒惰和异步。

答案 1 :(得分:0)

我想尝试新的Async API,这是一个不错的测试。事实上,有一个示例与Async Github主页上的用例非常接近。

Async是SIP,可能会在某些时候成为标准Scala的一部分。

除了使用await之外,这里的想法是你有一个在幕后使用异步逻辑的抽象add()方法。这样它就会被Calculator开发人员隐藏起来。

就个人而言,我也会添加API的异步版本,因此可以将Future传递出Calculator以与其他Future组合。

trait Calculator {
  def hardToCalculate1: Int
  def hardToCalculate2: Int
  def hardToCalculate3: Int

  def add(a: => Int, b: => Int): Int

  def result1 = add(hardToCalculate1, hardToCalculate2)
  def result2 = add(hardToCalculate2, hardToCalculate3)
  def result3 = add(hardToCalculate1, hardToCalculate3)
}

object So17677728 extends App with Calculator {

  override def add(a: => Int, b: => Int): Int = {
    val start = System.currentTimeMillis

    val res = Await.result(asyncAdd(a, b), 2000 millis)

    val end = System.currentTimeMillis

    println(s"Total time: ${end - start} ms")

    res
  }

  def asyncAdd(a: => Int, b: => Int): Future[Int] = {
    async {
      val fa = Future(a)
      val fb = Future(b)
      await(fa) + await(fb)
    }
  }

  val random = Random
  val maxSleep = 1000

  def hardToCalculate1: Int = htc(1)
  def hardToCalculate2: Int = htc(2)
  def hardToCalculate3: Int = htc(3)

  def htc(n: Int) = {
    val sleepMs = random.nextInt(1000)
    println(s"$n sleeping for $sleepMs ms")
    Thread.sleep(sleepMs)
    println(s"$n done sleeping")
    n
  }

  println(s"result1: $result1\n")
  println(s"result2: $result2\n")
  println(s"result3: $result3\n")
}

输出

1 sleeping for 438 ms
2 sleeping for 244 ms
2 done sleeping
1 done sleeping
Total time: 497 ms
result1: 3

3 sleeping for 793 ms
2 sleeping for 842 ms
3 done sleeping
2 done sleeping
Total time: 844 ms
result2: 5

3 sleeping for 895 ms
1 sleeping for 212 ms
1 done sleeping
3 done sleeping
Total time: 896 ms
result3: 4

或者在add Future中,您可以async进行理解,而不是await / {{1}}。