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
    。关键是要尽量让代码简单易懂,让那些不需要了解异步工作原理的人能够测试。但是,异步编写东西的方式肯定比纯未来有一些优势——需要花一些时间。是的,它真的很好