2012年4月3日火曜日

関数型とデーターフロー(5)

3月19日(月)に要求開発アライアンスのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』を行いましたが、説明を端折ったところを中心にスライドの回顧をしています。

今回は「KliesliでOption/Listを合成」として用意した以下のスライドを説明します。

関数型でデータフローを記述する方式を以下の事前準備の記事で考えました。

関数型とデータフロー(4)」では、右の図を作成しました。Kleisliを用いてOptionモナドを合成しています。

この図をスライドに収めるために簡略化したものが右の図です。

スライド作成時にListモナドの合成も入れておいた方がよいと考え、右の図も作成し、スライドには左右に併置する形で入れました。

この新しく追加した図「KleisliによるListモナドの合成」について「関数型とデータフロー(4)」にならってについて説明します。

Listモナドの合成

ScalazはKleisliのための一連の機能を提供しています。☆はモナドのbind演算に用いる関数をKleisliに入れる演算を行う関数です。

val plus5l = (a: List[Int]) => List(a.map(_ + 5))
val mul10l = (a: List[Int]) => List(a.map(_ * 10))
val plus5lk = ☆(plus5l)
val mul10lk = ☆(mul10l)

☆はkleisli関数の別名なので、以下のように書くこともできます。

val plus5lk = kleisli(plus5l)
val mul10lk = kleisli(mul10l)

☆(plus5l)によってKleisli化されたplus5lが、☆(mul10l)によってKleisli化されたmul10lが定義されます。それぞれにplus5lk、mul10lkという名前をつけています。

関数plus5lkとmul10lkはそれぞれ以下のように動作します。Int型を引数にとってOption[Int]型を返します。

scala> List(1, 2, 3) |> plus5l
res16: List[List[Int]] = List(List(6, 7, 8))

scala> List(1, 2, 3) |> mul10l
res25: List[List[Int]] = List(List(10, 20, 30))

plus5lkとmul10lkは、いずれもKleisli化されている(Kleisliの容器に入っている)ので、以下のように演算子>=>で合成することができます。合成して作成した新しいモナド演算を行う関数にp5m10okという名前をつけます。

scala> val p5m10lk = plus5lk >=> mul10lk
p5m10lk: scalaz.Kleisli[List,List[Int],List[Int]] = scalaz.Kleislis$$anon$1@2d688eb5

以下のように期待通りに動作しました。

scala> List(1, 2, 3) |> p5m10lk
res26: List[List[Int]] = List(List(60, 70, 80))

ノート

データフローの実現方式にListモナドを入れたのは、Listモナドを使った並列プログラミングへ言及したかったためで、セッションでは口頭でその点に触れることができました。

ただ、今見返してみるとこの図の範囲だと、Optionを使って以下のようにした方が自然そうです。

val plus5l = (a: List[Int]) => a.map(_ + 5).some
val mul10l = (a: List[Int]) => a.map(_ * 10).some
val plus5lk = ☆(plus5l)
val mul10lk = ☆(mul10l)
val p5m10lk = plus5lk >=> mul10lk

実行結果は以下のようになります。

scala> List(1, 2, 3) |> p5m10lk
res36: Option[List[Int]] = Some(List(60, 70, 80))

List処理を並列化する場合は、以下のようにパラレルコレクションを使いますが、この場合Kleisliの合成に使うモナドはListモナドではなくOptionモナドでもよいわけです。

scala> val plus5l = (a: List[Int]) => a.par.map(_ + 5).some
plus5l: List[Int] => Option[scala.collection.parallel.immutable.ParSeq[Int]] = <function1>
Promise

ここで、ListモナドやOptionモナドではなく、並列処理を専門に扱うモナドを導入するとさらに高度な並行プログラミングを行うことができます。そのような目的でScalazがサポートしているのがPromiseです。

Promiseについては以下のスライドが参考になります。

たとえば、以下のようにOptionモナドをKleisli化していたところを:

val plus5lk = ☆((a: List[Int]) => a.map(_ + 5).some)
plus5lk: scalaz.Kleisli[Option,List[Int],List[Int]] = scalaz.Kleislis$$anon$1@695a1b07

以下のようにすると:

scala> val plus5lk = ((a: List[Int]) => a.map(_ + 5)).promise
plus5lk: scalaz.Kleisli[scalaz.concurrent.Promise,List[Int],List[Int]] = scalaz.Kleislis$$anon$1@6f17b190

PromiseモナドをKleisli化されたものが返ってきます。(上側の型「scalaz.Kleisli[Option,List[Int],List[Int]]」と下側の型「scalaz.Kleisli[scalaz.concurrent.Promise,List[Int],List[Int]]」を比較するとイメージが湧いてくると思います。)このような形に持ってくると、Promiseモナドを使った高度な並行プログラミングが可能になるわけです。

関数やモナドの合成で、安全に高度な並行プログラミングを行うことが、今後のScalaプログラミングの方向性のひとつになってくるはずです。また、今回のセッションの中心的な話題であったデータフローモデルの実現手法という意味でも重要な技術です。

このあたりはKleisliやPromise以外にも色々な技がありそうなので、本ブログでも整理をしていきたいと思っています。

諸元

当日使用したスライドは以下のものです。(PDF出力ツールの関係で、当日は非表示にしたスライドも表示されています)

このスライド作成の様子は以下の記事になります。

まとめは以下の記事になります。

回顧は以下の記事になります。

0 件のコメント:

コメントを投稿