2012年7月26日木曜日

クラウド温泉3.0 (7) / 関数によるMonadicプログラミング

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その7です。

前回はMonadicプログラミングの非公式定義を考えました。まとめると以下になります。

Monadを中心としつつも、旧来からあるFunctorによるプログラミング、関数合成、コンビネータなども包含しつつ、パイプライン・プログラミングのスタイルとしてまとめたもの。

今回は、普通の関数呼出しによるMonadicプログラミングを考えます。普通の関数呼び出しなので当然モナドは使っていません。つまりパイプライン・プログラミングとしての関数呼び出しです。

パイプライン演算子

関数mul3とplus5を用意します。いずれも引数の数が1つの関数です。

def mul3(a: Int) = a * 3
def plus5(a: Int) = a + 5

これを普通に関数呼び出しで使うと以下のようになります。このように引数が一つの関数を連続的に呼び出すケースはパイプライン処理に見立てることができます。

scala> mul3(plus5(10))
res71: Int = 45

Scalazでは関数呼出しによるパイプライン処理を記述するための演算子として「|>」を提供しています。パイプライン演算子と呼ばれています。

このパイプライン演算子を使って上記処理を記述すると以下になります。まさにパイプライン演算ですね。

scala> 10 |> plus5 |> mul3
res73: Int = 45

複数の引数

関数の引数が複数あるケースを考えます。関数mulとplusを用意します。いずれも引数の数が2つの関数です。

def mul(a: Int, b: Int) = a * b
def plus(a: Int, b: Int) = a + b

これを普通に関数呼び出しで使うと以下のようになります。

scala> mul(3, plus(5, 10))
res74: Int = 45

ここで、この演算をScalazのパイプライン演算子で記述しようとすると、はたと困ってしまいます。これは、パイプラインのセマンティクスより、引数を複数持つ関数の呼出しの方がセマンティクスが大きいからです。より汎用的なわけですね。

関数呼び出しは、関数の実行結果をルートとする木構造として考えることができます。ルートからリーフに至る複数あるパスのそれぞれはパイプラインとして考えることができるので、複数のパイプラインを合成したものと考えることができます。

パイプラインのセマンティクスに持ち込むためには、複数あるパスの中の1つを主のパスと定め、このパスを中心にパイプラインを記述していく必要があります。

これを関数の呼び出しで実現するには、上記の複数引数による関数呼び出しを引数の数が1つの関数の関数呼び出しに変換します。なぜなら、関数の結果はひとつなので、これを関数がそのまま受け取るためには引数の数が1つでなければなりません。つまり、引数の数が1つの関数を用意し、これをパイプラインとして結合して動作させることになります。

部分適用

Scalaは関数の引数を部分的に適用した新しい関数を作成する、部分適用という機能を提供しています。部分適用を用いて記述すると以下になります。

scala> 10 |> (plus(5, _: Int)) |> (mul(3, _: Int))
res83: Int = 45

カリー化

関数がパイプラインの中で使われることが明らかな場合はカリー化しておくと便利です。カリー化した関数pluscとmulcは以下になります。

def mulc(a: Int)(b: Int) = a * b
def plusc(a: Int)(b: Int) = a + b

pluscとmulcを用いてパイプラインを構築すると以下になります。随分読みやすくなりました。この例からも分かるようにカリー化のコツは、パイプライン上で受け渡される引数を最後に持ってくることです。

scala> 10 |> plusc(5) |> mulc(3)
res78: Int = 45

便利かどうかは別として、curriedメソッドを用いて関数をその場でカリー化して使用することもできます。

scala> 10 |> (plus _ curried(5)) |> (mul _ curried(3))
res92: Int = 45

0 件のコメント:

コメントを投稿