CPS(型だけ)

CPS(継続渡し方式)変換をJavaScriptで説明してみるべ、ナーニ、たいしたことねーべよ - 檜山正幸のキマイラ飼育記 (はてなBlog) を見て頭がオーバーフローしたので、型だけで見ることにしてみた。

ズルして結果を先取りする。型Aをとって型Bを返す関数f

f::A->B

CPS変換f*は

f::A -> B	== CPS変換 ==>	f*::(B->X)->(A->X)

だよと。観察すると

  • f*は「関数を取って関数を返す」関数
  • AとBの順番が入れ替わってる。
  • Xはそのつど決まる型。

などなど。
Xが何になるのかは、例えば

f1::A->B
f2::B->C

みたいに、2つの関数があって、B型の値を受け渡す場合

f1::A->B	== CPS変換 ==>	f1*::(B -> C) -> (A -> C)
f2::B->C

と、最終的に返される結果の型(ここではC)になる。直後の関数の戻り型ではないっぽい。
たくさん関数が連なってたら

f1::A->B	== CPS変換 ==>	f1*::(B->E)->(A->E)
f2::B->C	== CPS変換 ==>	f2*::(C->E)->(B->E)
f3::C->D	== CPS変換 ==>	f3*::(D->E)->(C->E)
f4::D->E	== CPS変換 ==>	f4*::(E->E)->(D->E)

になる。
普通の書きかたの方だと計算は

  f1   f2   f3   f4
A -> B -> C -> D -> E

とつなげられる。型もあってる。同じくCPSの方もつなげられる。ただし逆順になる。

      f4*       f3*       f2*       f1*
(E->E) -> (D->E) -> (C->E) -> (B->E) -> (A->E)

型をあわせるにはこうつなげるしかない。
で、この2つが同値になって欲しいと。
ためしにf4*とf3*を合成して、(E->E)として恒等写像を取ると

       (f3*)○(f4*)
(E->E)      ->       (C->E)
id         ├>        f4○f3 ?

○は関数の合成。?は(C->E)な関数だから、f4○f3なんじゃないかと。
もしそうだとこうなる。

      f4*        f3*         f2*            f1*
(E->E) ->  (D->E) -> (C->E)   -> (B->E)      -> (A->E)
id    ├>    f4  ├> f4○f3  ├> f4○f3○f2 ├> f4○f3○f2○f1

いい感じ。普通に書いたのと同じ内容が作りあがった。

ほとんど型しか考えてなかったけど、実際にこうなるように実装できるか考える。

fのCPS変換f*の定義
def f*(cont)
  cont○f
end

これだけでいいんじゃないか。こう定義すれば、たとえば

f4(f3(f2(f1(x))))
f1*(f2*(f3*(f4*(id))))(x)

の2通りの書き方で同じ内容になる。

CPS変換ってこういうことなのかな。