Scala 2.10のFutureはモナドとして定義されていて、モナド的ないろいろな技が使えるようになっています。(参考:「Scala Tips / Validation (11) - モナド」)
また、Futureのコンビネータ、ユーティリティメソッドまわりは有用そうな機能が満載で、本来はこのあたりの基本機能から入るべきですが、個人的な趣味で今回はScalaz的なMonadicプログラミング向けの機能を取り上げます。
準備
例によって以下の関数を使います。
val g = (x: Int) => { Thread.sleep(x * 100) x }
REPL上で以下の設定が行われているものとします。
import scala.concurrent._ import ExecutionContext.Implicits.global import scala.util.{Success, Failure} import scala.concurrent.util.Duration
sequence
モナドを使い出すと、モナドの入れ子をどうさばくのかというのがプログラミング上のテクニックとなってきます。
そこで登場するのがScalazのsequenceメソッド。「Scala Tips / Validation (22) - sequence」など、このブログでも再三取り上げました。sequenceメソッドはモナドの入れ子(正確にはTraverseとApplicative)を扱う場合の定番イディオムとなっています。
たとえば、ListとSomeの入れ子は以下のように操作することができます。
scala> List(1.some, 2.some, 3.some).sequence res309: Option[List[Int]] = Some(List(1, 2, 3))
Scala 2.10 Future
seqeunceのFuture版です。FutureのListから関数のList全体をFuture化しています。
scala> val a = List(future(g(1)), future(g(2)), future(g(3))) a: List[scala.concurrent.Future[Int]] = List(scala.concurrent.impl.Promise$DefaultPromise@58276cfe, scala.concurrent.impl.Promise$DefaultPromise@464c4e9, scala.concurrent.impl.Promise$DefaultPromise@73bb9f3f) scala> val b = Future.sequence(a) b: scala.concurrent.Future[List[Int]] = scala.concurrent.impl.Promise$DefaultPromise@1b45fed7 scala> Await.result(b, Duration.Inf) res26: List[Int] = List(1, 2, 3)
Scalaz 6 Promise
参考にScalaz 6のPromiseです。Scala 2.10 Futureとほとんど同じになります。
scala> val a = List(promise(g(1)), promise(g(2)), promise(g(3))) a: List[scalaz.concurrent.Promise[Int]] = List(<promise>, <promise>, <promise>) scala> val b = a.sequence b: scalaz.concurrent.Promise[List[Int]] = <promise> scala> b.get res306: List[Int] = List(1, 2, 3)
traverse
traverseメソッドsequenceメソッドの親玉になるメソッドで、モナドの入れ子に対してより汎用的な処理を行うことができます。
scala> List(1, 2, 3).traverse(x => (x + 1).some) res315: Option[List[Int]] = Some(List(2, 3, 4))
Scala 2.10 Future
traverseのFuture版です。Listに対してfuture化した処理を適用して、関数のList全体をFuture化しています。
scala> val a = List(1, 2, 3) a: List[Int] = List(1, 2, 3) scala> val b = Future.traverse(a)(x => future(g(x))) b: scala.concurrent.Future[List[Int]] = scala.concurrent.impl.Promise$DefaultPromise@1d9b0076 scala> Await.result(b, Duration.Inf) res27: List[Int] = List(1, 2, 3)
Scalaz 6 Promise
参考にScalaz 6のPromiseです。Scala 2.10 Futureとほとんど同じになります。
scala> val a = List(1, 2, 3) a: List[Int] = List(1, 2, 3) scala> val b = a.traverse(g.promise) b: scalaz.concurrent.Promise[List[Int]] = <promise> scala> b.get res307: List[Int] = List(1, 2, 3)
ノート
まず、Futureのコンビネータ、続けてユーティリティメソッドを取り上げるつもりだったのですが、ユーティリティメソッドにsequenceとtraverseを見つけてしまい、先にこれを取り上げることにしました。
Scalazではsequenceやtraverseは、型クラスTraverseとApplicativeに対する処理でざっくりいうとTraverse[Applicative[A]]をApplicative[Traverse[A]]に変換する処理を行います。この処理が、モナドの入れ子操作が頻出するMonadicプログラミングでは非常に便利なわけです。
Scala 2.10 Futureでは、このsequenceとtraverseをTraversableOnceとFutureへのハードコーディングで実現しています。TraversableOnceはすべてのコレクションクラスの総親分なので、コレクション全体を対象にしているので実用的な意味での適用範囲は広いですが、Scalazのような型クラスを用いたアプローチより汎用性が制限されることになります。
Futureのsequenceやtraverseを見て感じたのは、モナドを効率よく操作するためにはこういったScalaz的(Haskell的)な機能は必要ということです。Futureだけのことを考えると、今回の実装のようなハードコーディングで凌ぐことができますが、これから陸続と新しいモナドがクラスライブラリに追加されてくることを考えると、いずれ破綻してしまうのは明らかです。
こうなった場合の対応策は、Scalazが採っているHaskell流の型クラス方式ということになるでしょう。Scalaの基本ライブラリがScalaz的なアプローチを取り入れるのか、Scalazが事実上の基本ライブラリとして併存するようになるのか、分かりませんが、モナドの利用が進むに従って型クラス方式の傾倒が進むのではないか。というのが、sequenceメソッド、traverseメソッドを見ての感想です。
諸元
- Scala 2.10.0-M7
- Scalaz 6.0.4
0 件のコメント:
コメントを投稿