2

I have an abstract trait with some requirements for calculations that are hard, and then some functions on the results of those calculations. I want to keep this trait simple so that it is easy to understand and test.

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

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

When I instantiate a Calculator, I'm going to use Futures to calculate those hardToCalculate values. Let's say they look like this:

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

So, I can construct a Future[Calculator] like this:

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

Then, I might use myCalc like this:

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

But when I do, I get this:

calculating 1
calculating 2
calculating 3
Result: 3

I'd like to only execute those futures if they are actually needed by the calculation I'm making. Even though I declared the hardToCalculates with lazy val, all three are calculated when the Future[Calculator].onComplete is executed.

One way to do this would be like this:

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)

This produces what I want:

calculating 1
calculating 2
result: 3

But now I have all of that Await blocking. What I really want is a Future[Calculator] that will execute the futures in a lazy way. Is this possible without introducing Futures into my Calculator trait? Any other suggestions about how to get what I'm after here?

(A gist with all of the above code is here.)

brandon
  • 675
  • 6
  • 10

2 Answers2

2

If you create a Future (using scala.concurrent.future) it will be calculated, no matter what you do. So you need a totally different strategy.

Further, your interface does not even remotely allow to figure out what data you will actually use. How should the calculation of myCalc know that in the onComplete you'll only use result1?

You could:

  • Use only lazy vals:

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

    Pro: Simple
    Con: Not asynchronous

  • Encapsulate the Future to allow requesting the calculation:

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

    But now you still have the problem that myCalc will request and wait for all of them. So you'll have to introduce ReqFutures in Calculator:

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

    Pro: When you call result1, only what you need is calculated (but still only once).
    Con: result1 is a Future[Int] now. So Futures have fully penetrated your Calculator.

If you cannot influence Calculator (what I am suspecting) and cannot change the code of result1,2,3 there is unfortunately nothing in my knowledge that you can do to make execution lazy and asynchronous.

gzm0
  • 14,752
  • 1
  • 36
  • 64
  • Thanks for the thoughtful answer. I can influence Calculator, but it will contain complex business logic, and I want to shield it (and the people who will be writing/testing it) from Futures, for comprehensions syntax, and the like. But, I want to use it with asynchronous code. Agreed that a new strategy is needed. – brandon Jul 15 '13 at 23:43
  • @brandon Someting that came to my mind while going to work: You could write a wrapper that, using macros and `Dynamic`, appears to be synchronous, but actually flatMaps the Futures. (You can also do that without the magic, but it's annoying since you have to write a full, but trivial adapter to your class). – gzm0 Jul 16 '13 at 14:59
  • That's exactly the kind of thing I was thinking about ... I was looking into macros, but I've never looked into `Dynamic`. Perhaps that's the final piece to the puzzle. – brandon Jul 16 '13 at 20:05
  • @brandon Have a look here: http://stackoverflow.com/questions/17580781/using-attachments-with-macros-in-scala-2-10 Travis tried to use dynamic to create custom types. He ran into the issue of having to read the structure storage multiple times. In your case that's a Scala type itself, so you should be fine. – gzm0 Jul 16 '13 at 20:30
  • @brandon Have a look here for a very simple example: https://github.com/gzm0/monwrap I will try to extend this to usable fragments of code (allowing to combine `MW`s, turning it into `flatMap`s) as I have run into similar issues in a project some time ago. – gzm0 Jul 17 '13 at 22:18
  • now *that's* interesting. Thanks ... looking forward to playing around with it. – brandon Jul 18 '13 at 05:41
0

I wanted to try out the new Async API and this was a nice test. In fact there is an example that is very close to your use case on the Async Github home page.

Async is a SIP and will likely be part of standard Scala at some point.

Besides using await the idea here is that you have an abstract add() method that uses asynchronous logic behind the scenes. That way it is hidden from the Calculator developers.

Personally I would add an async version of the API too so Futures could be passed out of the Calculator to be composed with other Futures.

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

Output

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

Alternatively in add you could Futures and a for comprehension instead of async/await.

sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • Thanks. Still beating my head on this one. It seems like I should be able to create some kind of wrapper class that allows me to use the simple, straightforward functions within Calculator, but supply the hard values (and retrieve the results) asynchronously. – brandon Jul 16 '13 at 18:15
  • Actually, my bad, using asynchronicity in Calculator would be good because the `hardToCalculate` methods could be done in parallel. I've replaced my answer. – sourcedelica Jul 17 '13 at 22:54
  • Thanks for the intro to async ... that may help with what I'm trying to do. I don't want to write asynchronous versions of all of the math that might be done within `Calculator`. The point is to try to keep that code as straightforward and easy-to-test as possible for people who don't need to understand how async stuff works. But, the async way of composing things definitely as some advantages over pure Futures -- need to spend some time with it. – brandon Jul 18 '13 at 05:40
  • Yes - it's really sweet – sourcedelica Jul 18 '13 at 14:24