PHP5.3で即時関数

Posted by Hiraku on 2011-09-10

JavaScriptではよく使う即時関数(function(){ /*...*/ })()ですが、PHPもバージョン5.3からクロージャがサポートされ、原理的には書けるはずなので試してみました。

current(array(function(){
  /*
   * このブロックは擬似的にブロックスコープを持つ
   *
   */
}))->__invoke();


echo current(array(function($a, $b){
    return $a + $b;
}))->__invoke(1, 2);// 3

…すっげー見づらいですが、一応解説。

function(){ }をリファレンスにする

PHPの関数や配列、オブジェクトといったものは、一度変数に代入しないとうまく起動してくれません。しかし関数の戻り値はメソッドをつなげることができます。anatooさんのHackが有名ですね。

function ref($o) {
    return $o;
}

echo ref(new DateTime)->format('Y');

このref()を、PHPの組み込み関数だけで実現しようとするとcurrent(array())となります。ひとまずこれでリファレンスになりました。

->__invoke()で起動する

しかし関数を起動する()をそのまま続けても文法エラーになってしまいます。ここで思い出したいのはPHPの無名関数はClosureクラスのインスタンスであるということです。実際は__invoke()というマジックメソッドを呼んでいるのを、シンタックスシュガーとして短く書けるようになっているだけです。

なので、省略せずに直接書けば期待通りに動きます。

current(array(function(){
  /*
   * このブロックは擬似的にブロックスコープを持つ
   *
   */
}))->__invoke();

しかし読みにくいですね。。。

読みやすくする

call_user_func()を使うと、もう少しすっきり書けます。

call_user_func(function(){
  /*
   * このブロックは擬似的にブロックスコープを持つ
   *
   */
});

echo call_user_func(function($a, $b){
    return $a + $b;
}, 1, 2); //3

これぐらいなら、ややこしくなりがちな不動点コンビネータだってそこそこの読みやすさで書けますよ!!

/**
 * 不動点コンビネータ(Yコンビネータ)
 */
function Y($f) {
    return call_user_func(function($x) use($f){
        return function($y) use($f, $x){
            return call_user_func($f($x($x)), $y);
        };
    }, function($x) use($f){
        return function($y) use($f, $x){
            return call_user_func($f($x($x)), $y);
        };
    });
}

/**
 * 階乗計算を無名関数で行う
 */
echo call_user_func(Y(function($f){
    return function($n) use($f){
        return $n
             ? $n * $f($n - 1)
             : 1;
    };
}), 5); // 120

Wikibooksをほとんどそのままパクリました。。)

PHPの文化では「クラスで名前もしっかり付けましょう」な感じなのであまり馴染まないかもですが、書き方のバリエーションが広がるのはいいことだと思います。テスティングフレームワークとかDSL作るときに面白くなりそう。

keyword: PHP php5.3

PHPの最新記事