Scala 用未来满足抽象特征需求
我有一个抽象的特点,对计算有一些要求,这很难,然后对这些计算的结果有一些函数。我想保持这个特点的简单,以便它易于理解和测试Scala 用未来满足抽象特征需求,scala,akka,future,Scala,Akka,Future,我有一个抽象的特点,对计算有一些要求,这很难,然后对这些计算的结果有一些函数。我想保持这个特点的简单,以便它易于理解和测试 trait Calculator { def hardToCalculate1: Int def hardToCalculate2: Int def hardToCalculate3: Int def result1 = hardToCalculate1 + hardToCalculate2 def result2 = hardToCalculate2
trait Calculator {
def hardToCalculate1: Int
def hardToCalculate2: Int
def hardToCalculate3: Int
def result1 = hardToCalculate1 + hardToCalculate2
def result2 = hardToCalculate2 + hardToCalculate3
def result3 = hardToCalculate1 + hardToCalculate3
}
当我实例化一个计算器时,我将使用Futures来计算那些难以计算的值。假设它们看起来像这样:
def f1 = future {
println("calculating 1")
1
}
def f2 = future {
println("calculating 2")
2
}
def f3 = future {
println("calculating 3")
3
}
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 onComplete {
case Success(calc) => println("Result: " + calc.result1)
}
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)
因此,我可以像这样构建一个未来[计算器]
:
def f1 = future {
println("calculating 1")
1
}
def f2 = future {
println("calculating 2")
2
}
def f3 = future {
println("calculating 3")
3
}
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 onComplete {
case Success(calc) => println("Result: " + calc.result1)
}
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
calculating 3
Result: 3
我只想执行那些期货,如果它们是我正在计算的实际需要的话。尽管我用lazy val
声明了hardToCalculate
s,但这三个值都是在执行Future[Calculator].onComplete
时计算出来的
一种方法是:
def f1 = future {
println("calculating 1")
1
}
def f2 = future {
println("calculating 2")
2
}
def f3 = future {
println("calculating 3")
3
}
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 onComplete {
case Success(calc) => println("Result: " + calc.result1)
}
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
但是现在我有了所有的等待
阻塞。我真正想要的是一个未来[计算器]
,它将以一种懒惰的方式执行未来。如果不在我的计算器中引入未来,这是否可能?关于如何得到我想要的东西,还有其他建议吗
()如果您创建一个未来
(使用scala.concurrent.Future
),无论您做什么,它都将被计算出来。所以你需要一个完全不同的策略
此外,您的界面甚至不允许远程计算您实际使用的数据。myCalc
的计算如何知道在onComplete
中您将只使用result1
你可以:
- 仅使用惰性VAL:
val calculator = new Calculator {
lazy val hardToCalculate1 = {
println("calculating 1")
1
}
// ...
}
赞成:简单
缺点:不是异步的
- 封装
未来
以允许请求计算:
class ReqFuture[T](body: () => T) {
lazy val fut = future { body() }
}
但是现在您仍然有一个问题,myCalc
将请求并等待所有这些。因此,您必须在计算器中引入ReqFutures
:
trait Calculator {
def hardToCalculate1: ReqFuture[Int]
// ...
def result1 = for {
h1 <- hardToCalculate1.fut
h2 <- hardToCalculate2.fut
} yield h1 + h2
}
特征计算器{
def硬计算1:ReqFuture[Int]
// ...
def result1=for{
h1我想尝试新的,这是一个很好的测试。事实上,在Async Github主页上有一个非常接近您的用例的示例
Async是一个很好的解决方案,在某个时候可能会成为标准Scala的一部分
除了使用wait
之外,这里的想法是,您有一个抽象的add()
方法,它在后台使用异步逻辑。这样,它就对计算器的开发人员隐藏了
我个人也会添加一个异步版本的API,这样Future
s就可以从Calculator
中传递出来,与其他Future
s组成
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
s和a以供理解,而不是async
/wait
感谢您深思熟虑的回答。我可以影响计算器,但它将包含复杂的业务逻辑,我想保护它(以及将编写/测试它的人)来自Futures,用于理解语法等。但是,我想将其用于异步代码。同意需要一种新策略。@brandon在开始工作时想到的一些事情:你可以编写一个包装器,使用宏和动态
,看起来是同步的,但实际上是未来的平面映射。(您也可以在不使用魔法的情况下实现这一点,但这很烦人,因为您必须为类编写一个完整但微不足道的适配器)。这正是我一直在考虑的事情……我一直在研究宏,但我从来没有研究过动态
。也许这是谜题的最后一部分。@brandon看看这里:Travis试图使用动态创建自定义类型。他遇到了必须多次读取存储结构的问题。在您的ca中se这本身就是一种Scala类型,所以应该没问题。@brandon在这里看一个非常简单的示例:我将尝试将其扩展到可用的代码片段(允许组合MW
s,将其转换为flatMap
s)因为我不久前在一个项目中遇到了类似的问题。谢谢。我还在为这个问题绞尽脑汁。似乎我应该能够创建某种包装类,允许我在计算器中使用简单、直接的函数,但提供硬值(并检索结果)异步。事实上,我的缺点是,在计算器中使用异步会很好,因为hardToCalculate
方法可以并行完成。我已经替换了我的答案。感谢异步的介绍…这可能有助于我尝试做的事情。我不想编写异步版本的所有可能在计算器中完成的数学Calculator
。关键是要尽量让代码简单易懂,让那些不需要了解异步工作原理的人能够测试。但是,异步编写东西的方式肯定比纯未来有一些优势——需要花一些时间。是的,它真的很好