QIQの設計と実装

QIQエクステンションがやっていることについて。
PHPというかZend Engineでは、コンパイラとエクゼキュータが関数ポインタになっており、それぞれファイルの内容から実行コードを生成するzend_comiple_file、文字列から実行コードを生成するzend_compile_string、実行コードに対応するハンドラを呼び出すzend_executeが定義されています。また、それらのデフォルト実装としてcompile_file()、compile_string()、execute()があります。
言語としては良いか悪いかは別にして、堅くも柔らかくもない独自のポジションにあるPHPですが、コアのZend Engineはコンパイラやエグゼキュータが差し換えられたり、実行時のフックが追加できたりと、実は拡張性に富んでいるのです。でもマルチスレッドと演算子オーバーローディングだけは勘弁な!
QIQではオリジナルのyaccファイル (zend_language_parser.y) に少しだけ手を加えたものから独自の構文解析器qiqparseを生成し、zend_comiple_fileとzend_comiple_stringを組み込みの構文解析器zendparseの代わりにqiqparseを使う関数qiq_compile_fileとqiq_compile_stringで置き換えています。qiq_compile_fileとqiq_compile_stringの中身は、zendparseがqiqparseになっている点を除いては、オリジナルのcompile_fileおよびcompile_stringと同じです。また、字句解析器はそのままzendlexを使っています。実行に関してzend_executeはそのままにしていますが、無名関数とクロージャをサポートするためのユーザ定義実行コードハンドラを登録しています (下図では省略)。
実行とコンパイル: PHPとPHP+QIQ
実行とコンパイル: PHPとPHP+QIQ posted by (C)rsk

一方、APCやeAccelerator等のキャッシュやXdebugのようなデバッガでは、いったんzend_compile_file (ものによってはzend_compile_stringやzend_executeも) が指す関数を別の関数ポインタに退避させた後、zend_compile_fileを独自の関数で上書きしています。その関数では一般的に〈前処理〉→〈元の関数を実行〉→〈後処理〉という処理をしており、理論上は複数組み合わせることも可能です。相性の問題は多分にあると思われますが。
実行とコンパイル: PHP+APC
実行とコンパイル: PHP+APC posted by (C)rsk

しかし、QIQのコンパイラは元のコンパイラを呼ばないので、APC等より先にロードされなければなりません。もし後からロードされた場合はキャッシュが無効になるばかりでなく、予期しない問題が起こる可能性もあります。
この点にさえ注意すれば、QIQのコンパイラが生成する実行コードはクロージャために未使用のイベントを転用している以外はオリジナルのものと全く同じで、クロージャを生成するイベントもユーザ定義ハンドラで処理するので、キャッシュとの互換性も確保されています。
また、Xdebugでも独自のユーザ定義ハンドラを登録していますが、QIQのものとは被っていません。
実行とコンパイル: PHP+QIQ+APC
実行とコンパイル: PHP+QIQ+APC posted by (C)rsk

というわけで、「必ず他のエクステンションより先にロードする」ことさえ守れば、QIQはキャッシュやデバッガとも連携できます。いわんやパッチ版をや。

続々・Zend Engine Hack (クロージャもあるよ) (拡張モジュールもあるよ)

拡張モジュール版もできました。普通のPHP extensionとしても使えますし、Zend extensionとしてAPCやeAccelerator等より先にロードすればキャッシュも効きます。ただし、ZEND_APIで修飾されていない (dllexportされていない) 関数を多用しているため、Windows向けにはビルドできません。
モジュール名のQIQは「PHPの次」を意味します。読み方はたぶん「くいっく」。「きゅーあいきゅー」でもおk。小文字でPHPと並べると字面が対照的なのが気に入っています。
dvd btb
qiq php

Windows版はCLICGIの両方をビルドし、モジュールはmbstring, PCRE, SQLite, SimpleXML等を静的に組み込み、GDとeAcceleratorのDLLも同梱しています。
この拡張モジュール・パッチでは下記の機能を提供します。

無名関数

まずはおなじみ(?)の無名関数から。構文は function (引数) { 処理 } で、これ全体が無名関数を表す式として扱われます。式なので、変数に代入するときは $func = function () { return 1; }; と、セミコロンで終端する必要があります。array_map(function($n){return $n*$n;}, $numbers); のように、コールバック関数としても使えます。参照を返す無名関数は書けません。

<?php
$list = range(0, 5);
usort($list, function($a, $b){ return $b - $a; });
print_r($list);

結果:

Array
(
    [0] => 5
    [1] => 4
    [2] => 3
    [3] => 2
    [4] => 1
    [5] => 0
)


create_function()は、ただ直感的に書けないだけでなく、ループで使うと呼び出される度に新しい関数を生成するので、知らないうちにメモリを大量に使用することがあります。例えば、以下のようなコードを実行した場合は、3つの無名関数が生成され、リクエスト終了まで破棄されません。

<?php
$lists = array(range(0, 5), range(5, 10), range(10, 15));
foreach ($lists as &$list) {
    usort($list, create_function('$a, $b', 'return $b - $a;'));
}
print_r($lists);


create_functionでは、関数はまず“__lambda_func”という一時的な名前の関数として評価され、改めて“\0”から始まる、通常のスクリプトではパースエラーとなる名前で再登録されます。
lambda: create_function()
lambda: create_function() posted by (C)rsk


“__lambda_func”は再登録後に破棄されますが、create_function()を呼び出した時に関数__lambda_func()が定義されているとE_ERRORレベルのエラーが発生します。

<?php
function __lambda_func() {}
create_function('', '');
Fatal error: Cannot redeclare __lambda_func() (previously declared in ...


一方、無名関数は1度しかコンパイルされないので、何回ループ中で使っても大丈夫です。もちろん__lambda_func()が定義されていても大丈夫です。

<?php
$lists = array(range(0, 5), range(5, 10), range(10, 15));
foreach ($lists as &$list) {
    usort($list, function($a, $b){ return $b - $a;});
}
print_r($lists);


無名関数は通常の関数と同じく、コンパイル時の一意なキーと、小文字に正規化された呼び出される際のキーで登録されます。シンボルテーブルのエントリは2つありますが、関数の実体であるop_arrayは一つで、参照カウント方式で管理されています。コンパイル時の名前で登録されたエントリのスタティック変数用シンボルテーブルstatic_variablesは常にNULLですが、関数がスタティック変数を持つ場合は、呼び出される名前で登録されているエントリには固有のスタティック変数用シンボルテーブルが割り当てられます。関数呼び出しの際には小文字に正規化されたキーで関数をシンボルテーブルから検索するので、コンパイル時の名前で登録されたエントリが実際に使われることはありません。

lambda: 無名関数・インライン無名関数
lambda: 無名関数・インライン無名関数 posted by (C)rsk


また、無名関数は宣言の直後に()と、必要があれば引数を指定してインライン呼び出しもできるので、PHPの大雑把なスコープに代わるブロックスコープとしても利用できます。インライン呼び出しの際、左辺には無名関数ではなく、無名関数の戻り値が代入されます。

<?php
$var = 'foo';
$ret = function(){ $var = 'bar'; var_dump($var); return $var; }();
var_dump($var, $ret);

結果:

string(3) "bar"
string(3) "foo"
string(3) "bar"

クロージャ

無名関数宣言の前に“static”を付けると、クロージャになります。クロージャは初期化されていないか、値がnullのスタティック変数の値を、宣言されたスコープの同名の変数から取り込みます。

<?php
function get_counter($initial = 0)
{
    $count = $initial;
    return static function(){
        static $count;
        return ++$count;
    };
}

$c1 = get_counter();
$c2 = get_counter(1);
$c3 = get_counter(2);

var_dump($c1(), $c2(), $c3());
echo "-\n";
var_dump($c1(), $c2(), $c3());
echo "-\n";
var_dump($c1(), $c2(), $c3());

結果:

int(1)
int(2)
int(3)
-
int(2)
int(3)
int(4)
-
int(3)
int(4)
int(5)


クロージャは必要とされる度に作成され、オリジナルの無名関数とop_arrayを共有し、独自のスタティック変数用シンボルテーブルを持ちます。create_function()よりコストは低いのですが、反復回数の多いループ中で使うのはおすすめしません。

lambda: クロージャ
lambda: クロージャ posted by (C)rsk


無名関数と同じく、インライン呼び出しもできます。クロージャをインライン呼び出しした場合は、前に登録されていたクロージャを破棄し、同じキーで登録します。これによってメモリの浪費が抑えられるので、ループでも安心して使えます。

<?php
for ($i =0; $i < 5; $i++) {
    var_dump(static function($n){
        static $i;
        return pow($n, $i);
    }($i));
}

結果:

int(1)   // 0の0乗
int(1)   // 1の1乗
int(4)   // 2の2乗
int(27)  // 3の3乗
int(256) // 4の4乗

lambda: インラインクロージャ
lambda: インラインクロージャ posted by (C)rsk


クロージャのスタティック変数はオリジナルから複製されるため、全てのスタティック変数がnull以外で初期化されている場合も、生成されたクロージャは別の関数としてふるまいます。逆に、スタティック変数をもたないクロージャを定義した場合は、E_STRICTレベルのエラーが発生し、複製は行われずオリジナルの関数を返します。また、以下のようにして無理矢理オリジナルのスタティック変数を更新すると、それ以後は期待通りのクロージャが生成されなくなってしまいます (普通はこんなことしないだろうけど、念のため解説)。

<?php
function get_counter($initial = 0)
{
    $count = $initial;
    return static function(){
        static $count;
        return ++$count;
    };
}

$c1 = get_counter();
$c2 = get_counter(1);
$c3 = get_counter(2);

$c0 = substr($c1, 0, strrpos($c1, '.'));
$c0(); // $count が null++ = 1 で固定される

$c4 = get_counter(3); // "3" が$countに代入されない
$c5 = get_counter(4); // "4" が$countに代入されない


キーワードに既存の“static”を使ったのは字句解析器はそのままに、構文解析器の変更だけで対応したかったからです。クラスメソッドの“static”修飾子と紛らわしいとも考えましたが、静的変数の解決に定義されたスコープを使う無名関数だからよしとします。何が「よし」なのか書いている本人も疑問ですが、とにかくよしとしてください。

再帰のために

以前のパッチでは、無名関数の再帰呼び出しために__FUNCTION__の挙動にアドホックな変更をしていたのですが、それを止め、現在の関数がシンボルテーブルに登録されているキーを取得するための関数get_current_function_key()と、キーをローカル変数に代入する関数set_current_function_key()を実装しました。これらの関数では無名関数やクロージャの正しいキーを取得できます。関数外で呼び出された場合はE_WARNINGレベルのエラーが発生し、falseを返します。

<?php
// 1から10までカウントアップ
function($from, $to){
    static $self = false;
    if (!$self) $self = get_current_function_key();
    var_dump($from++);
    if ($from > $to) return;
    $self($from, $to);
}(1, 10);

// 1から10までカウントアップ
// set_current_function_key()はキーを第1引数で指定されたローカル変数に代入し、その変数名を返す
// 引数を省略した場合は“${''}”にキーを代入し、空文字列を返す
function($from, $to){
    var_dump($from++);
    if ($from > $to) return;
    ${set_current_function_key()}($from, $to);
}(1, 10);

new/cloneからはじまるメソッドチェーン

“new ClassName”や“clone $object”を“$()”で囲むと、囲んだ式を変数のように扱うことができます。これによって、$(new SomeClass($param))->method(); とメソッドチェーンを書けるようになります。コンストラクタに引数を渡さないときは $obj = new SomeClass; と同じく、括弧を省略して $(new SomeClass)->method(); と書くこともできます。もちろんメソッドだけでなく、プロパティにも同様にアクセスできます。

角括弧で配列を宣言/リスト代入

array()シンタックスシュガーとして [] が使えるようになります。キーと値の区切りは => です。 list() の代わりに $[] でリスト代入もできます。

<?php
$a = [1, 2, 3];
$b = ['foo' => 'hoge', 'bar' => 'fuga', 'baz' => 'piyo'];
print_r($a);
print_r($b);
$c = 'Hello';
$d = 'World';
$[$c, $d] = [$d, $c];
var_dump($c, $d);

結果:

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
Array
(
    [foo] => hoge
    [bar] => fuga
    [baz] => piyo
)
string(5) "World"
string(5) "Hello"

おまけ

無名関数・クロージャには以下のような別の構文もあります。次のリリースでは廃止します。

<?php
$lambda = $(($a, $b){ return $a * $b; }); // 無名関数
$closure = static $((){ static $i; return ++$i; }); // クロージャ

続々・Zend Engine Hack (クロージャもあるよ)【予告編】

※このパッチ・バイナリでは下記の機能の一部は利用できません。


Windows版はid:shimookaさんのhttp://www.doyouphp.jp/inst/inst_win_vcxe.shtmlを参考にVC++ 2005 でビルドしました。CLIのみで、モジュールは必要最低限のものしか組み込んでいません。

ひとまず仕様は固まったけど解説を書く時間がないので予告だけ。

  • 無名関数 / function(){ echo "Hello, Anonymous World!\n"; }();
  • クロージャfunction get_closure($foo) { return static function(){ static $foo; echo $foo; }; }
  • get_current_function_key() / function($n) { $f = get_current_function_key(); $f($n+1); }
  • set_current_function_key() / function($n) { ${set_current_function_key()}($n+1); }
  • new/cloneからはじまるメソッドチェーン / $(new SomeClass)->someMethod(); $(clone $obj)->property;
  • 角括弧で配列を宣言 / [1, 2, 3, 'foo' => 'hoge', 'bar' => 'fuga', 'baz' => 'piyo']
  • '$' + 角括弧でリスト代入 / $a = 'A'; $b = 'B'; $[$a, $b] = [$b, $a];

厳密にはタイトルは「続・Zend Engine Hack」が正しい表記なのですが、それはまた別のお話。ちなみに今後このネタを書くときは「新・〜」「また又・〜」「ニュー・〜」「痛快・〜」とタイトルが変化するかどうかは定かではありません。

TextMateでPHPプログラミング(PHP勉強会 at 大阪 発表資料)

いつもながらイベントのレポートが遅くて恐縮です。PHP勉強会 at 大阪の発表資料を公開します。
今回もSlideShareにはねられ、handsOutにもはねられたのでPDFの直DLです。CIDフォント(ロダン)は使わず、OTFとTTFだけ(ヒラギノ角ゴ Pro + Myriad Pro + アニト-等幅)だけにしたのですが...。次の機会からは別の方法を考えないといけませんね。
TextMateでPHPプログラミング (PDF)


さて、会場のApple Store心斎橋店には2, 3度ほど行ったことがあったけど、2階に上がるのは初めて。立ち見が出るほどの大盛況でした。PHPのスキルの分布も初心者の方から濃い方まで満遍なくおられたようです。
えー、自分のプレゼンではAppleStoreのMacを使っての発表で、自分のマシンと勝手が違ったこともあり (言い訳100%)、かなりgdgdでした。TextMateのおいしいところが伝えきれなかったのではと思います。(そもそもLTと言うには枚数多すぎ、時間長すぎという話も...)
TextMateの特徴はスニペットをはじめとした強力かつ豊富な機能であるのは間違いないのですが、使っているうちに最大の利点は「単にサクサクコードが書ける」ことのように思えてきました。こればっかりは実際に使ってみないと分からないので、マカーなプログラマにはぜひ一度TextMateを使ってみることをお勧めします。本格的に「Webアプリの仕事で」使えるようになるのはマルチバイトにちゃんと対応する(はず)の2.0からですが、今でもPHPエクステンションとかはTextMateで書いています。
懇親会にはウコンの力を準備しておくほど飲む気まんまんで臨みました。PHPerの集まりとなると、例のはなしが出ないはずがないのですが、結局、PHPが広く使われている理由は「初心者にやさしい」と宣伝されているからではなく (あるいはそう宣伝されている理由は)、「日本語 (母語) で読めるオンラインマニュアルが充実している」これに尽きるわけです。Rubyは素晴らしい言語だし、Rubyのドキュメントを書かれている方々も頑張っておられるとは思うのですが、どう見てもマニュアルだけはPHPの圧勝です。そういえば、昨年のオープンソース関西の後の飲み会では「言語の自由度が制限されている分、初心者でも上級者のコードを読んで勉強しやすい」という話もありました。あとver_dump()最強とか。


id:ku-sukeさん、主催お疲れさまでした。田舎に住んでいると、なかなか技術的な交流の機会がないので、比較的近く (といっても片道2時間) でイベントがあるのは嬉しいです。次の機会にも参加したいと思います。

諸君 私は珈琲が大好きだ

諸君 私はPHPが好きだ
諸君 私はPHPが大好きだ

"諸君、私はPHPが大好きだ" - ぐらめぬ・ぜぷつぇんのはてダ(2007 to 2011)

昔、大学の卒論の中間発表のとき、壁紙を自作の「諸君 私は珈琲が大好きだ〜」にしていたのを思い出した。
いま思うと狂っていたとしか思えない。怖いもの知らずとかいうレベルではなくて、怖いという概念を知らなかったのかもしれない。
幸いにしてその壁紙は失われている。もし現存していたら、きっとやめておいたほうがいいと知りつつも公開していたに違いない。
つまり、そのあたりは全く成長していないのである。

HELLSING 4 (ヤングキングコミックス)

HELLSING 4 (ヤングキングコミックス)

今日のPEARニュース (1/31)

DB_DataObject 1.8.8HTML_Template_Flexy 1.3.0がリリースされました。DB_DataObjectはバグ修正のみ、Flexyは新機能も追加されています。

New Features
#7609 - support user defined compilers as compiler object
#11741 - getOptions / clearOptions - thx to justdev
#---- - add experimental toJSON feature. flexy:toJSON
#12265 - support label in Element
#----- - change setlocale to use LC_MESSAGES to prevent bugs with strtoupper
#10248 - attributes: flexy:content, flexy:replace, flexy:omittag
#8008 - flexy:include src= allow variables { }
#9436 - support background for url rewriting
http://pear.php.net/package/HTML_Template_Flexy/download/1.3.0/

flexy:includeで変数が使えるようになったり、新しい属性がいくつか追加されていたり*1Flexy使いには嬉しい内容です。


今日の勉強会、くまっち先生がAlan Knowlesネタをやってくれますように。

*1:これに関しては後日改めて記事を書きます