Future
scalaz-concurrentのFutureを使ってみる。バージョンは7.1だ。
scala.concurrent.Futureと同じ意味だが、それの強化版だ。
Futureのscaladocを見てみよう。
- Trampolineを使っているから、map/flatMapなモナディックな処理をしても一定のスタックしか使わない。
- scala-2.10のFutureと違って、map/flatMapごとに新しくspawnしないし、ExecutionContextのimplicitも必要ない。
とのことらしい。
やたらscalaのFutureをdisっているので、scalazのFutureはより良いものなのだろう。
使ってみよう
まず確認用の関数を定義しておく。
def calc[A](a: A) = { Thread.sleep(1000); println(s"calc $a"); a }
Future.applyに処理を渡し、runで同期的に実行。(タイムアウトをつける場合はrunFor)
scala> Future(calc(1)).run
calc 1
res31: Int = 1
非同期で実行する場合は、start。
scala> Future(calc(1)).start
res32: scalaz.concurrent.Future[Int] = Suspend(<function0>)
calc 1
明示的だ。
onComplete的なことをしたい場合は、runAsync。
scala> Future(calc(1)).runAsync(i => println(s"i is $i"))
calc 1
i is 1
ここで一旦applyの定義を見てみよう。
/** Create a `Future` that will evaluate `a` using the given `ExecutorService`. */
def apply[A](a: => A)(implicit pool: ExecutorService = Strategy.DefaultExecutorService): Future[A] = Async { cb =>
pool.submit { new Callable[Unit] { def call = cb(a).run }}
}
java.concurrentのを使っている。scalaのFutureと違いとても明確で分かりやすいコードだ。(scalaのFutureは謎に複雑で読むの辛い。Futureに限らないかもだけど...)
処理をつなげてみよう。
mapを使う。
scala> Future(calc(1)).map(i => calc(i + 1)).run
calc 1
calc 2
res22: Int = 2
順番に実行される。この辺の挙動はscalaのFutureと同じく、completeしてから次の処理が続く。
もちろんforもできる。
scala> for { a <- Future(calc(1)); b <- Future(calc(2)) } yield a + b
res39: scalaz.concurrent.Future[Int] = BindAsync(<function1>,<function1>)
scala> .start
res40: scalaz.concurrent.Future[Int] = Suspend(<function0>)
calc 1
calc 2
処理をまとめて受け取る。
Future.sequenceみたいに同時に走らせて結果をまとめるものもあった。
scala> Future.gatherUnordered( Future(calc(1)) :: Future(calc(2)) :: Nil ).run
calc 2
calc 1
res54: List[Int] = List(1, 2)
結果を合わせる。(Monoidのmappend)
scala> Future.futureInstance.aggregate( Future(calc(1)) :: Future(calc(2)) :: Nil ).run
calc 1
calc 2
res159: Int = 3
Future.futureInstanceはNondeterminismのインスタンスだ。
まとめて結果を受け取り、順番通りに取得する。
both: 2つ並列に実行
scala> Future.futureInstance.both(Future(calc(1)), Future(calc(2))).run
calc 1
calc 2
res171: (Int, Int) = (1,2)
mapBoth: 2つ並列に実行後、callback関数を呼ぶ。
scala> Future.futureInstance.mapBoth(Future(calc(1)), Future(calc(2))) { case (a, b) => a + b }.run
calc 2
calc 1
res174: Int = 3
nmap3: 3つ並列に実行後、callback関数を呼ぶ。
scala> Future.futureInstance.nmap3(Future(calc(1)), Future(calc(2)), Future(calc(3))) { case (a, b, c) => a + b + c }.run
calc 1
calc 3
calc 2
res176: Int = 6
nmap6まである。
例外処理は含まない
scalazのFutureは例外処理を含まないので、scalaのFutureのように例外処理を扱いたいときは、Taskを使う。
次はTaskを見るとしよう。
今日はここまで。