序章
Scala のFuture
( 2.10で新しく、現在は 2.9.3 ) はアプリカティブ ファンクターです。これは、トラバース可能な type がある場合、と 関数F
を取り、それらを に変換できることを意味します。F[A]
A => Future[B]
Future[F[B]]
この操作は、標準ライブラリで として利用できますFuture.traverse
。Scalaz 7は、ライブラリからtraverse
applicative functor インスタンスをインポートする場合に使用できる、より一般的なものも提供します。Future
scalaz-contrib
traverse
ストリームの場合、これら 2 つのメソッドの動作は異なります。標準ライブラリのトラバーサルは、返す前にストリームを消費しますが、Scalaz のトラバーサルはすぐにフューチャーを返します:
import scala.concurrent._
import ExecutionContext.Implicits.global
// Hangs.
val standardRes = Future.traverse(Stream.from(1))(future(_))
// Returns immediately.
val scalazRes = Stream.from(1).traverse(future(_))
Leif Warnerがここで観察しているように、別の違いもあります。標準ライブラリtraverse
はすべての非同期操作をすぐに開始しますが、Scalaz は最初の操作を開始し、それが完了するのを待ち、2 番目を開始し、それを待つというように続きます。
ストリームの異なる動作
ストリームの最初の値に対して数秒間スリープする関数を作成することで、この 2 番目の違いを示すのは非常に簡単です。
def howLong(i: Int) = if (i == 1) 10000 else 0
import scalaz._, Scalaz._
import scalaz.contrib.std._
def toFuture(i: Int)(implicit ec: ExecutionContext) = future {
printf("Starting %d!\n", i)
Thread.sleep(howLong(i))
printf("Done %d!\n", i)
i
}
Future.traverse(Stream(1, 2))(toFuture)
次のように出力されます。
Starting 1!
Starting 2!
Done 2!
Done 1!
そして Scalaz バージョン ( Stream(1, 2).traverse(toFuture)
):
Starting 1!
Done 1!
Starting 2!
Done 2!
これはおそらく、私たちがここで望んでいるものではありません。
リストの場合は?
不思議なことに、この 2 つのトラバーサルは、リスト上でこの点で同じように動作します — Scalaz は、1 つの Future が完了するのを待たずに、次の Future を開始します。
もう一つの未来
Scalaz には、独自の Futureconcurrent
実装を備えた独自のパッケージも含まれています。上記と同じ種類のセットアップを使用できます。
import scalaz.concurrent.{ Future => FutureZ, _ }
def toFutureZ(i: Int) = FutureZ {
printf("Starting %d!\n", i)
Thread.sleep(howLong(i))
printf("Done %d!\n", i)
i
}
そして、ストリームと同様にリストのストリームでの Scalaz の動作を取得します。
Starting 1!
Done 1!
Starting 2!
Done 2!
それほど驚くことではないかもしれませんが、無限ストリームをトラバースしてもすぐに戻ります。
質問
この時点で、要約する表が本当に必要ですが、リストで行う必要があります。
- 標準ライブラリ トラバーサルを使用したストリーム: 戻る前に消費します。それぞれの未来を待たないでください。
- Scalaz traversal を使用したストリーム: すぐに戻ります。それぞれの未来が完了するのを待ちます。
- ストリームを持つ Scalaz の先物: すぐに戻る; それぞれの未来が完了するのを待ちます。
と:
- 標準ライブラリ トラバーサルを含むリスト: 待つ必要はありません。
- Scalaz traversal を使用したリスト: 待つ必要はありません。
- リスト付きの Scalaz フューチャー: 各フューチャーが完了するまで待ちます。
これは意味がありますか?リストとストリームに対するこの操作の「正しい」動作はありますか? 「最も非同期的な」動作、つまり、戻る前にコレクションを消費せず、各 Future が完了するのを待ってから次の Future に進むことをここで表現しない理由はありますか?