PHP でクロージャを実現する「QIQ」がステキ

PHPクロージャを実現する「QIQ」という拡張モジュールがあるそうだ。

これによると、QIQ を使うと PHP で以下が可能になるという。

  • 無名関数
  • クロージャ
  • new/cloneからはじまるメソッドチェーン
  • 角括弧で配列を宣言/リスト代入


どうやって実現しているかというと、なんと拡張モジュールのくせに PHP構文解析を乗っ取ってしまうそうだ。その発想はなかったわ

あまりにすごそうなので、実際に使ってみた。

インストール

環境は MacOS X 10.4。PHP は自前コンパイル

$ wget http://www.opendogs.org/pub/php_qiq-0.5.0.tgz
$ tar xzf php_qiq-0.5.0.tgz
$ cd php_qiq-0.5.0/
$ phpize
$ ./configure
$ make
$ make test
$ sudo make install
$ ls -F /usr/local/lib/php/extensions/no-debug-non-zts-20060613/
qiq.so*

使い方

拡張モジュールで構文解析関数を乗っ取るという仕組みなので、QIQ をロードする部分は実行したいスクリプトから分離しておく必要がある。もちろん php.ini で読み込んでいる場合はこの限りではない。


main.php:

<?php
/// QIQ をロード
if (! extension_loaded('qiq')) {
  dl('qiq.so');
}
/// そのあとに実行したいスクリプトを読み込む
require('example.php');
?>


example.php:

<?php

class Hello {
    var $name;
    function __construct($name) {
        $this->name = $name;
    }
    function hello() {
        echo "Hello {$this->name}!\n";
    }
}

function test_qiq() {
    /// 無名関数
    $add = function($x, $y) { return $x + $y; };
    echo $add(10, 20);     #=> 30
    echo "\n";

    /// クロージャ
    $n = 0;
    $count = static function() { static $n; return ++$n; };
    echo $count(), "\n";   #=> 1;
    echo $count(), "\n";   #=> 2;
    echo $count(), "\n";   #=> 3;

    /// new/cloneからはじまるメソッドチェーン
    $(new Hello('world'))->hello();    #=> Hello world!

    /// 角括弧で配列を宣言/リスト代入
    $a = [10, 20, "x"=>1, "y"=>2];
    var_export($a);  #=> array (0=>10, 1=>20, 'x'=>1, 'y'=>2)
}

test_qiq();

?>


実行結果:

$ php main.php
30
1
2
3
Hello world!
array (
  0 => 10,
  1 => 20,
  'one' => 1,
  'two' => 2,
)


ステキ!


高階関数も扱える。

<?php
/// 関数を返す関数
function generate_func($prefix, $postfix) {
    return static function($s) {
        static $prefix, $postfix;
        return $prefix . $s . $postfix;
    };
}

$cssfile = generate_func('css/', '.css');
$imgfile = generate_func('img/', '.png');

echo $cssfile('foo'), "\n";   #=> css/foo.css
echo $imgfile('foo'), "\n";   #=> img/foo.png

気づいた点

クロージャで外の変数に代入しても、これはクロージャの中だけの話であり、外の変数が更新されるわけではないようだ。その意味でいえば、これは本当のクロージャとは呼べないけど、まあ PHP だしあまり細かいことは気にしない。

<?php
$x = 0;
$f = static function() {
    static $x;
    ++$x;   // 一見、外の変数を更新しているようにみえる
    return $x;
};

echo $f(), "\n";   #=> 1
echo $x,   "\n";   #=> 0   // しかし、実際には更新されない
echo $f(), "\n";   #=> 2
echo $x,   "\n";   #=> 0   // これもしかり
?>


このような場合、Python だと list を使うのが常套手段だが、PHP だと配列を使ってもうまくいかないようだ。reference を試したけどだめだった。

<?php
$a = array(0);
$f = static function() {
    static $a;
    ++$a[0];
    return $a[0];
};

echo $f(),  "\n";   #=> 1
echo $a[0], "\n";   #=> 0
echo $f(),  "\n";   #=> 2
echo $a[0], "\n";   #=> 0
?>


クラスを定義したらうまくいった。

<?php
class Value {
    var $val;
    function __construct($init) { $this->val = $init; }
}

$v = new Value(0);
$f = static function() {
    static $v;
    $v->val++;
    return $v->val;
};

echo $f(),    "\n";   #=> 1
echo $v->val, "\n";   #=> 1
echo $f(),    "\n";   #=> 2
echo $v->val, "\n";   #=> 2
?>

結論

QIQ すげー。まじすげー。これを使えば、PHP を次の次元へと進めることができる。まさに PHP++!*1


はっきり言おう、この PHP はモテる! 2008 年は PHP 復活の年だ!

*1:命名の由来が PHP++ → QIQ だそうです。