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