あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

富豪的プログラミングにおける関数合成の効用

あくまで富豪的プログラミングが許される時という前提付きですが、関数合成がとても有用だと思うので、記事にしてみます。なお、ソースコードはCoffeeScriptで記述しているので*1、JavaScriptはわかるけど、CoffeeScriptはわからないという方は、適宜、CoffeeScript公式サイトのREPL(上部にあるTRY COFFEESCRIPTというリンクより)等で変換しながらご確認ください。また、JavaScriptエンジンはV8を想定しているので、一部のブラウザでは動かないメソッドも入っています。

Lisp脳について

に、Lisp脳についてこう書いてあります。

手続き的な発想では、毎回特殊な処理を行いそれを繰り返すという発想でプログラミングしていました。

Schemeプログラマはそうは考えません。

データからデータへの変換を考えれば良く、出力は後からどうにでもなる、と考えています。

データからデータへの変換は、例えば元データが配列だったりすると、処理のたびに配列を走査する必要があるので、本来なら配列の走査は一度で良いということを考えると、一種の富豪的プログラミングになります。

この富豪的プログラミング時の関数合成の効用を考えます。

関数合成

合成関数という言葉があって、なにやら数学で習うらしいのですが、数学は全然わからないので、習った記憶すらないレベルです。

Wikipediaの写像の合成がそれに該当するのだと思います。

とりあえず、難しいことは置いておいて、(f ∘ g)(x) = f(g(x))が成り立つ関数を作ります。

合成関数を作るための関数を定義する

ex1

これがf(g(x))を実行した結果です。(f ∘ g)(x)な関数を定義できるようにするため、composeという関数を作ります。

ex2

こんな感じです。

引数に関数を3つ以上指定できるようにする

上記の状態では、composeの引数に指定できる関数は2つしかありません。これだと3つ以上関数を合成したい時に少し面倒です。

ex3

引数の入れ替えが面倒だし、composeのネストは少々読みづらいですね。

可変長引数に対応したバージョンのcomposeを定義します。Underscore.jsにあるcomposeも可変長引数を取れます。

ex4

可変長引数版の前は、読みづらかった上に、途中で関数を追加する際に、引数の順番を調整しないといけなかったりして面倒でした。可変長引数版の方が圧倒的に利便性が高いと思います。

関数合成の有用性

では、準備が整ったので、関数合成の有用性を説いてみたいと思います。「Lisp脳」の謎に迫ると同じ、fizzbuzzを例にしてみます。

こんな感じで記述することができますね。

ex5

で、効用の話です。

3の倍数の時と3が付く数字の時はアホにしてほしい、あとアホより優先度低くていいので、Buzzはそのままで*2

こんな依頼が後からやってきた時、関数合成だったら割と修正が容易です。

最後の3行くらいを追加・変更しました。

ex6

compose内で、値の変換に作用する関数のうち、不要になった関数を消して、toAho1とtoAho2を追加して、順序を入れ替えました。一応、仕様が変わったので、関数名も変えていますが。

こんな感じで、関数をくっつけたり、剥がしたりしてプログラミングができる点が、関数合成の強みなのかなと思います。

注意点

このような関数合成を使ってプログラミングをする場合は、前の関数の返り値に強く依存する引数を取る関数を作って当てはめると、順番の入れ替えがしにくくなります。

なので、関数の実行順を入れ替えても問題ないように、関数を書いていく必要があります。そうすることで、ひとつひとつの関数がシンプルな小さな関数になり、組み合わせしやすい関数になるんじゃないかなと思っています。

おまけ

もっと汎用的にしたい場合は、関数合成と組み合わせて、部分適用を使うと便利です。部分適用はCoffeeScriptだと超簡単にできます。

exx

無駄に長くなってる感は否めませんが、汎用的にはなってるような気がします。

CoffeeScriptの場合は(x) -> (y) -> /* 関数本体 */と書くだけで部分適用できるので、活用しやすいです。

まとめ

富豪的プログラミングが許される状況においては、関数合成を使うことで、関数の切り貼り入れ替えがしやすくなり、柔軟性の高いプログラムを作りやすくなると思います。

私は富豪なSchemerになりたい。

*1:はてなダイアリーはCoffeeScriptのスーパーpre記法対応してないっぽい...

*2:強引過ぎてごめんなさい