2012年3月14日水曜日

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

要求開発アライアンスのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』で使用するスライドについて背景説明を行っています。

今回は背景説明第11弾として、「関数型とデータフロー(3)」として用意した以下の図を説明します。



モナド

今回はモナドを使って、データフローのパイプラインの振舞いをよりリッチなものにしてみます。

今回使用するのは、成功パイプラインと失敗パイプラインの2つのパイプラインを持つモナドであるOptionモナドです。正常系の演算は成功パイプライン上で行われます。Optionモナドの異常系は、エラー発生時点で即失敗パイプラインに移行し、そのままデータフローを終了させます。エラー情報の通知もありません。

以下ではまずOptionモナドで使用する関数オブジェクトを説明した後、Optionモナドの動作について説明します。

モナドで使用する関数オブジェクト

図ではモナドで使用する関数plus5oとmul10oを定義しています。どちらも、Int型を引数に取って、Option[Int]を返す関数です。Scalazを用いて簡潔に記述しています。

plus5o

関数plus5oの定義は以下の通りです。

val plus5o = (a: Int) => (a != 5).option(a + 5)

前回、前々回に使用した関数plus5のOptionモナド版です。関数plus5では、引数にInt値を取り、返却値もInt値でしたが、モナド版のplus5oでは、引数にInt値を取るのは同じですが、返却値はOption[Int]となり、本来の返却値をOptionモナドに包んで返します。

Optionモナドを返すので、関数が成功したのか失敗したのかをOptionのSomeまたはNoneとして通知できるようになりました。引数が5の場合は失敗としてNone、それ以外は成功で引数に5を加えた値をSomeに包んで返します。

このようにOptionモナドを返す関数(正確には型Tを取りOption[T]を返す関数)は、Optionモナドのbind演算(ScalaではflatMapメソッド、Scalazでは>>=メソッド)に適用することができます。

さらに汎用的にいうと、型Tを取りモナドM[T]を返す関数はモナドM[T]のbind演算に適用することができます。今回の例はOptionモナドですが、他にもListモナド、EitherモナドなどScalaでは多数のモナドが用意されています。

普通にScala的に書くと以下のようになります。

val plus5o = (a: Int) => if (a != 5) Some(a + 5) else None
mul10o

関数mul10oの定義は以下の通りです。

val mul10o = (a: Int) => (a % 10 != 0).option(a * 10)

引数が10で割り切れる場合は失敗としてNone、それ以外は成功で引数を10倍した値をSomeに包んで返します。

普通にScala的に書くと以下のようになります。

val mul10o = (a: Int) => if (a % 10 != 0) Some(a * 10) else None
参考

Scala文法上の参考情報です。

関数オブジェクトplus5oを関数リテラルを用いて記述しましたが、これを普通の書き方で関数として定義すると以下のようになります。

def plus5od(a: Int): Option[Int] = {
  if (a != 5) Some(a + 5)
  else None
}

この関数を関数オブジェクトにするには、もう一段以下の処理を行います。

val plus5o = plus5od _

Optionモナドの動作

Optionモナドを使ったデータフローの配線は以下のものになります。

(_: Int).some >>= plus5o >>= mul10o

Int型の値を受け取りSomeに包んで、plus5o関数、mul10o関数に流していきます。概念的には、図にあるようにOptionの箱の中を左から右へデータが流れていきます。

前回、前々回のデータフローと違うのは正常系のパイプラインと異常系のパイプラインの2系統のパイプラインが用意されていることです。

以下ではそれぞれのパイプラインの動作についてみていきましょう。

正常動作

データフローにSome(3)を流すと、データフローの計算は成功し、計算結果としてSome(80)が出力されます。

plus5oでエラー

データフローにSome(5)を流すと、データフローの計算は失敗し、計算結果としてNoneが出力されます。

5はplus5oでエラー判定されるため、plus5oから失敗側のパイプラインが動作します。

mul10oでエラー

データフローにSome(15)を流すと、データフローの計算は失敗し、計算結果としてNoneが出力されます。

15はplus5oでは正常な値なので、plus5oの結果は20となりますが、この20がmul10oでは10で割り切れるためエラー判定され、mul10oから失敗側のパイプラインが動作します。

ノート

以上で説明したようにモナドを使うことで、データフローを構成するパイプラインに特殊な振舞いを付加することができます。

ここでは最も単純な(しかし強力なのでScalaプログラミングでは必須の)Optionモナドを使用しましたが、この他にも様々な種類のモナドが提供されています。

たとえば、条件によってパイプラインを切り替えながら動作させる場合にはEitherモナドが有効です。

また、複数の値を同時に流す場合はListモナドやStreamモナドを使います。この場合は、パイプラインを並行実行させ、並行処理を行うことも可能です。各パイプラインの動作の待合せも自動的に行ってくれます。

モナドやMonadicプログラミングというとかなり敷居が高い感じがしますが、ここで説明したようにデーターフローをパイプラインで実行するようにイメージするとずいぶん印象が変わってくると思います。

0 件のコメント:

コメントを投稿