コルーチン改め継続

php_continuation-0.0.1devel.tgz


別に C言語レベルでコンテキストスイッチしなくても、関数が呼び出される際の状態を保存・復元すれば似たようなことができるじゃん! と思いつき、変数のシンボルテーブルの複製をリソースにすることでコルーチンっぽい操作を実現できました。
モジュール名の候補は Coroutine, Fiber, Continuation, Closure などがあり、yield で移譲するのでなく return した後も状態を維持するということで Continuation を採用しました。


このモジュールでは continuation 型のリソースと、二つの関数を提供します。

  • resource continuation co_create(callback $func[, array $initial_vars])
    • コールされたスコープの変数すべて (のコピー) がコールバック関数で暗黙のうちに利用できるようになり、その値の変更も維持される。
    • コールバック関数内で定義された変数は co_call する度に初期化される。
    • また、連想配列でインジェクションする変数のリストを指定することもできる。この場合は他のローカル (あるいはグローバル) 変数はコピーされない。
  • mixed co_call(resource continuation $co[, array $args])
    • 返り値は co_create() に与えられた関数の返り値。
    • 配列でコールバック関数に渡される引数のリストを指定することもできる。(call_user_func_array($func, $args) のように動作する)


以下は examples にある使用例と、その実行結果です。

fibonacci.php (フィボナチ数列)

script
<?php
extension_loaded('continuation') || dl('continuation.so') || exit(1);

function fibonacci()
{
    list($a, $b) = array($b, $a + $b);
    return $b;
}

$a = 0;
$b = 1;
$fib = co_create('fibonacci', compact('a', 'b'));
for ($i = 0; $i < 10; $i++) {
    var_dump(co_call($fib));
}
output
int(1)
int(2)
int(3)
int(5)
int(8)
int(13)
int(21)
int(34)
int(55)
int(89)

incrementer.php (加算)

script
<?php
extension_loaded('continuation') || dl('continuation.so') || exit(1);

function incrementer($x = null)
{
    $a += $i;
    if (is_int($x)) {
        $a += $x;
    }
    return $a;
}

function generate_incrementer($a, $i)
{
    return co_create('incrementer');
}

$a = 0;
$i = 1;
$n = 0;

var_dump($a, $i, $n);

$co1 = co_create('incrementer');
$co2 = co_create('incrementer', array('a' => -1, 'i' => 2));
$co3 = co_create('incrementer', array('a' => 100, 'i' => 0));
$co4 = generate_incrementer(100, 1);

for ($t = 0; $t < 5; $t++) {
    $result = array(
        'co1' => co_call($co1),
        'co2' => co_call($co2),
        'co3' => co_call($co3, array(--$n)),
        'co4' => co_call($co4),
    );
    print_r($result);
}

var_dump($a, $i, $n);
output
int(0)
int(1)
int(0)
Array
(
    [co1] => 1
    [co2] => 1
    [co3] => 99
    [co4] => 101
)
Array
(
    [co1] => 2
    [co2] => 3
    [co3] => 97
    [co4] => 102
)
Array
(
    [co1] => 3
    [co2] => 5
    [co3] => 94
    [co4] => 103
)
Array
(
    [co1] => 4
    [co2] => 7
    [co3] => 90
    [co4] => 104
)
Array
(
    [co1] => 5
    [co2] => 9
    [co3] => 85
    [co4] => 105
)
int(0)
int(1)
int(-5)