最近CPSに凝っているらしいid:higeponさんが:
人間の皮が裏返る。外が内、内が外。みたいなイメージでしょうか。
と書いてます(CPSで思考する - higepon blog)。この裏返る感じを説明してみたいと思います。
プログラムが、3つの部分A, B, Cから構成されているとします。
A; B; C;
A, B, Cは単一の文ではなくて大きなブロックでもかまいません。ブロックAの計算結果は変数aに代入され、ブロックBの計算結果は変数bに代入されるとしましょう。それぞれのブロックを関数化すると、次のようになります。
a = A(); b = B(a); c = C(b);
変数cの値がプログラム全体の結果となります。
中間変数a, bは不要なので除去すると:
c = C(B(A()));
結局、プログラム全体の機能は、C(B(A()))) という入れ子の関数呼び出しで表現できます。ここで、入れ子の内側から見ると、A, B, C の順序であることに注意してください。
次にCPS(継続渡し方式)を使ってみましょう。関数BをCPS変換したものをB*とします。Bに引き続く処理はCなので、CをB*に渡す形に変換すれば:
a = A(); c = B*(C)(a);
関数B*(C)を一時的にFと置くと:
a = A(); c = F(a);
今度は、関数AのCPS変換をA*として:
c = A*(F)();
Fをもとに戻して、
c = A*(B*(C))();
形を整えるために、何もしない関数をIとして、CのCPS変換C*を使って、
c = A*(B*(C*(I)))();
出来上がった式 A*(B*(C*(I)))() は、入れ子の内側からの順序が C, B, A になっています。関数結合(合成)を「・」で表すともっと見やすくて:
- 単純な関数化:(C・B・A)()
- CPSへの変換:(A*・B*・C*)(I)()
素直な関数化に対してCPS変換は確かに裏返ってます。
この事実の背景は、CPS変換(実は米田埋め込み)が反変関手だということです。