Help us understand the problem. What is going on with this article?

言語によってちょっと違うFuture/Promiseをまとめてみた(3)

More than 1 year has passed since last update.

続き

前回 のJavaScirpt に続いて今回は Scala です。

以下を見ながらまとめました。

Scala の Future

scala.concurrent.Future から見ていきます。

概要

  • Futureの生成(処理内容の登録)は Future コンパニオンオブジェクトの apply に処理を渡せばよい。
    • コードの見た目は Future { ... } になる。
    • 小文字始まりの future { ... } はなくなる予定らしい
  • Future を生成するとすぐに、implicit な ExecutionContext によってスレッドが生成され、実行開始される。もちろん自分で ExecutionContext を指定もできる
    • import scala.concurrent.ExecutionContext.Implicits.global が必要
  • 結果の取得は f.valueSuccessFailure が入っている

  • 終了を待ちたいときは Await.result(mytask, timeout) を使う。例外を受け取らない Await.ready もある

  • 終了時コールバックは Future のインスタンスの onComplete に正常・エラーの両方の処理を渡す

Future[Int] mytask = future { heavyTask() } // 本当は重い処理を書く
mytask onComplete {
  case Success(result) => // 成功時の処理
  case Failure(ex)     => // 失敗時の処理
}
  • onSuccessonFailure を使ってコールバックを登録してもよい
mytask onSuccess {
  case result => // 成功時の処理
}
mytask onFailure {
  case ex     => // 失敗時の処理。例外の型を指定するとマッチしたときだけ発火する
}
  • コールバックを何度も登録したときの呼び出し順は保障されない

Future の合成

  • scala らしく map で合成する。
  • flatMap も使えるので for comprehention でも書ける
  • 失敗は recover または recoverWith`(あたらしいFutureを返す場合) で受け取れる

コード例

5とかx+1とかは全部「時間のかかる処理」だと思ってください
val f = Future { 5 }
val g = f.map { case x => x + 1 } foreach println 
// 6 が表示される

val h = f.flatMap { case x => Future { x + 1 } } foreach println 
// 6 が表示される

val f = for {
  x <- Future { 5 }
  y <- Future { x + 1 }
} yield y + 2
Await.result(f, Duration("10 millis"))
// 5, x+1, y+2 がそれぞれ終了次第順次実行して 8 が表示される

val f1 = Future { Thread.sleep(5000); 5 }
val f2 = Future { Thread.sleep(5000); 3 }
(for { x <- f1; y <- f2 } yield (x+y)) foreach println
// 2つの sleep を並列実行して、5秒後に 8 が表示される
// sbt console からだと上記3つを急いで実行しないとよくわからないことになります
// 先ほどの順次実行の例のようにfor の中に Future の生成を書いてしまうと、
// f1 が終わるまで f2 が始まらないので並列実行にならないことに注意

val h = f.flatMap { 
  case x => Future { x / 0 } 
} recover {
  case x => 10
} foreach println 
// ゼロ除算でエラーになるので recover が走り 10 が表示される
  • transform は 成功時と失敗時の処理をそれぞれ指定できる
  • filter は条件に合致しないと失敗扱い。
  • andThen は指定した処理の結果を使わず、元の結果をそのまま次に送る (tap 的なもの)

複数の Future の扱いはまだちょっと整理できていないです。
scala - Wait for several Futures - Stack Overflow
Scala - Futureをマスターする -コンパニオンオブジェクト操作- - Qiita

  • sequence : Seq[Future[T]] -> Future[Seq[T]]
  • traverse : Seq[T] -> Seq[Future[T]]

Scala の Promise

  • p.future で Future を生成できる
  • p.success で Future を成功させられる、つまり onSuccessが呼ばれる
  • p.failure で Future を生成できる、つまり onFailure が呼ばれる
  • success か failure どちらか一回だけしか呼べない。2回呼ぶと呼んだ側で Exception が発生する

Promise の利用例

import scala.concurrent.{Await, Future, Promise}
import scala.concurrent.ExecutionContext.Implicits.global

def heavyFuture = {
  val p = Promise[Int]
  Future {
    Thread.sleep(1000)
    val result = 10
    p.success(result)
  }
  p.future
}

val f = heavyFuture
f onSuccess { case x => println(x) }

まとめ:言語ごとの違いについてわかってきたこと

チェーンする対象の名前

今までなんとなく コールバックの入れ子を使わないで済むようにチェーンの仕組みを提供する責任は Promise が持っているものだと勘違いしていましたが、処理系によって結構呼び名がずれていました。

非同期処理の実行開始のタイミング

実行開始のタイミングがよくわかっていなかったのは Java のイメージ(Future作ってからExecutorでたたく)に引きずられていたから、みたいです。むしろ Future 作ったらそこで動き始めるイメージの方が合っているようです。

表にしてみました

処理系 実行開始のタイミング チェーンする対象の名前
Java5 Future に処理を定義し、ExecutorService.execute に Future を渡した時 チェーンできない
Java8 ComplatableFuture に処理を渡した時 CompletionStage
ECMAScript6 Promise を new したとき? Promise
Deferred Future の then を呼び出した(定義した)時? Promise
scala Future を生成した時 Future

Promise と Future の関係のイメージ

「Promise が 一回だけの成功・失敗を保障する」 という説明も納得はしたのですが、Future とのセットでのとらえ方としては Future と Promise と独立した別物があるというよりも、

  • Promise は Future の性質のこと
  • Future には単体の Future と『Promise付きの Future』とがある
  • Promise付きの Future が「一回だけ成功か失敗かする」
  • 処理をチェーンしていく対象は Future

という理解が自分にはスッキリ来ました。多分色々な説明もそういうことを言っているのだと思いますが腹に落ちてなかったです ^^;

Promise の作り方のイメージ

  • 何らかのコンテナ的なものの中で Promise を作っておく。
  • コンテナの中で Future を作って、中で重い処理を実行して先ほどの Promise を終了させる。
  • 受け側は コンテナから Promise を取り出して後続処理をチェーンしていく
処理系 コンテナ的なもの 重い処理の渡し方 チェーン対象の取り出し方 チェーン方法
ECMAScript6 Promise p = new Promise(func(res,rej) { ...; res(r) }) p p.then
Deferred $.Deferred d = $.Deferred(func(success,err) { ...; success(r) }) p = d.promise p.then
scala の Promise Futureを作るための自作関数 val p = Promise; Future { ...; p.success(r) } f = p.future f.map

宿題

  • Java8 の Promise の有無についてまだちゃんと調べていないことに気付きました。
  • Play2Java や Rxも調べてみようと思いつつ放置
reki2000
3Dエンジンの中身とかCPUを作るとかコンパイラを作るなど低レイヤ好きです。8bit時代のネタにはよく釣られます。昔から C、SQL、Java、Scala をよく使い、最近は Python や Go を使うことが多いですが、Julia や Rust がやりたいことに向いている気がしてきており勉強中です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away