CPS関数をループ実行する

CPS(Continuation-Passing Style)の関数をループ実行するのに使えるユーティリティモジュールを試してみた。

今回試したのは、

なお、ここではCPS関数とは単に「結果をコールバック呼び出しで返す関数」全般と考えている。もしかするとこれって、厳密にはCPSとは言わないのかもしれない。


コードはこんな感じ。

結果:

--- sync, CPS::kloop
number: 1000, stack_num: 6
--- sync, Async::Defer
Deep recursion on subroutine "Async::Defer::done" at ./cps_loop.pl line 141.
Deep recursion on anonymous subroutine at /usr/local/share/perl/5.14.2/Async/Defer.pm line 342.
Deep recursion on subroutine "main::sync_cps_add" at ./cps_loop.pl line 142.
Deep recursion on subroutine "Async::Defer::continue" at /usr/local/share/perl/5.14.2/Async/Defer.pm line 398.
number: 1000, stack_num: 6007
--- sync, AnyEvent::Tools::async_repeat
number: 1000, stack_num: 8
--- async, CPS::kloop
number: 1000, stack_num: 11
--- async, Async::Defer
number: 1000, stack_num: 13
--- async, AnyEvent::Tools::async_repeat
number: 1000, stack_num: 8

CPS関数では、コールバックがCPS関数内で同期的に呼ばれる場合と、AnyEventなどを使ってCPS関数外で非同期的に呼ばれる場合がある。同期的に呼ばれる場合、注意しないと関数の再帰呼び出しがループ回数に比例して繰り返されてしまう危険性がある。

上の例では同期型CPS関数と非同期型CPS関数を用意し、1000回ループさせている。

CPSモジュールでは、内部でtail callをうまく使っているので同期型CPS関数をループさせてもコールスタックが深くならないのが素晴らしい。

Async::Deferモジュールは非同期型関数を前提に使われているので、同期型CPS関数をループさせるとコールスタックが深くなってしまう。今回はuse warningsしているので警告が出ている。

AnyEvent::ToolsモジュールはAnyEventに依存していて、コールバックの実行はAnyEventに任せているので、同期型CPS関数でもコールスタックが深くならないのだと思う。そのかわり、同期型CPS関数をループさせる場合でもどこかでAnyEventのcondition variableをrecvしないといけない。