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 ?>