続・Zend Engineハック

無名関数がかなり使いやすくなりました。高階関数のような記述もできます。
たとえば、これの結果は

<?php
function($func){
    $func('Hello, Anonymous World!');
}(function(){
    return function($str){
        echo $str, PHP_EOL;
    };
}());

こうなります。

Hello, Anonymous World!

激しく使えるので、英語の壁を乗り越えてphp.internalsに投げたくなってきました。


すべての変更点は以下の通りです。

  • 同じ無名関数を返すステートメントを複数回実行できない問題を修正。
    • 無名関数の生成はコンパイル時の1回だけなのでループ中に書いてもcreate_functionのようにメモリを浪費しない。
  • 無名関数を直接コールできるように改良。
    • JavaScriptの無名関数のように使える。
    • スコープを汚染せずにコードが実行可能。
  • 無名関数名 (おかしな表現ではある) を"anonymous#<serial>" に変更。
    • 関数名に"#"を含むので通常の関数名と衝突しない。
  • Callable Objectをコールバック関数として使用するための __callback__ 擬似型へのキャストをサポート。
    • 右値が文字列または配列ならそのまま、Callbaleインターフェイスを実装したクラスのインスタンスならarray($object, "call")に、それ以外は文字列にキャストする。
    • 本当はPHPコードでキャストするよりコールバック関数を引数に取る関数側で対応したいが、変更する箇所が多すぎる。


パッチのダウンロード:


無名関数と角括弧の配列リテラルの使用例:

<?php

// 角括弧の配列リテラル
var_dump([0, 1, 2], ['foo': 'orange', 'bar': 'apple', 'baz': 'lemon']);

// 三項演算子や角括弧構文とも衝突しない
var_dump((1 == 2) ? 1 : 2, $array = [(1 == 2) ? 1 : 2 : 0, 1 : 2], [$array[1], $array[2]]);


// 無名関数を作成・実行
$func = function($n =1){
    return implode(' ', array_fill(0, $n, __FUNCTION__));
};
var_dump($func, $func(2));
ReflectionFunction::export($func);


// 無名関数でソート
$array = str_split('0120-APPLE-1');
print_r($array);
usort($array, function($a, $b){
    if (is_numeric($a) && is_numeric($b)) {
        return $a - $b;
    } elseif (is_string($a) && is_numeric($b)) {
        return -1;
    } elseif (is_numeric($a) && is_string($b)) {
        return 1;
    }
    return strcmp($b, $a);
});
print_r($array);


// 無名関数を直接実行
function(){
    printf("%d: %s\n", __LINE__, __FUNCTION__);
}();

// もちろん引数もとれる
function($n){
    for ($i = 0; $i < $n; $i++) {
        printf("%d: %s\n", __LINE__, __FUNCTION__);
    }
}(3);

// 無名関数内で宣言された関数は外側のスコープに影響しない
var_dump(isset($i));

// もちろん値を返すこともできる
$result = function(){
    return sprintf('%d: %s', __LINE__, __FUNCTION__);
}();
var_dump($result);

// 直でもOK
var_dump(function(){
    return sprintf('%d: %s', __LINE__, __FUNCTION__);
}());

// ループでも使える
for ($i = 0; $i < 3; $i++) {
    function(){
        printf("%d: %s\n", __LINE__, __FUNCTION__);
    }();
    function(){
        printf("%d: %s\n", __LINE__, __FUNCTION__);
    }();
}


// ネストもOK
function($func){
    $func('Hello, Anonymous World!');
}(function(){
    return function($str){
        echo $str, PHP_EOL;
    };
}());


// 無名関数は実際には anonymous# + 連番 の関数名で定義される
$i = 1;
while (function_exists("anonymous#$i")) {
    $i++;
}
var_dump($i);

ReflectionFunction::export(sprintf("anonymous#%d", $i - 1));

結果:

array(3) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
}
array(3) {
  ["foo"]=>
  string(6) "orange"
  ["bar"]=>
  string(5) "apple"
  ["baz"]=>
  string(5) "lemon"
}
int(2)
array(2) {
  [2]=>
  int(0)
  [1]=>
  int(2)
}
array(2) {
  [0]=>
  int(2)
  [1]=>
  int(0)
}
string(11) "anonymous#1"
string(23) "anonymous#1 anonymous#1"
Function [ <user> function anonymous#1 ] {
  @@ /example.php 11 - 13

  - Parameters [1] {
    Parameter #0 [ <optional> $n = 1 ]
  }
}

Array
(
    [0] => 0
    [1] => 1
    [2] => 2
    [3] => 0
    [4] => -
    [5] => A
    [6] => P
    [7] => P
    [8] => L
    [9] => E
    [10] => -
    [11] => 1
)
Array
(
    [0] => P
    [1] => P
    [2] => L
    [3] => E
    [4] => A
    [5] => -
    [6] => -
    [7] => 0
    [8] => 0
    [9] => 1
    [10] => 1
    [11] => 2
)
36: anonymous#3
42: anonymous#4
42: anonymous#4
42: anonymous#4
bool(false)
string(15) "51: anonymous#5"
string(15) "57: anonymous#6"
63: anonymous#7
66: anonymous#8
63: anonymous#7
66: anonymous#8
63: anonymous#7
66: anonymous#8
Hello, Anonymous World!
int(12)
Function [ <user> function anonymous#11 ] {
  @@ /example.php 75 - 77

  - Parameters [1] {
    Parameter #0 [ <required> $str ]
  }
}


Callable Objectの使用例:

<?php

// 関数のように使えるオブジェクト
class Proc implements Callable
{
    public function call()
    {
        echo "Hello, World!\n";
    }
}

$proc = new Proc();
$proc();
// call_user_func等に使うには__callback__擬似型へキャストするか
// array($proc, 'call') としなければならない
call_user_func((__callback__)$proc);


// 比較クラス
class Compar implements Callable
{
    public function call($a, $b)
    {
        return $a - $b;
    }
}

$array = range(1, 10);
shuffle($array);
print_r($array);
usort($array, (__callback__)new Compar);
print_r($array);


// 置換クラス
class Replacer implements Callable
{
    public function call($matches)
    {
        return $matches[0] . ' ' . $matches[0];
    }
}

echo preg_replace_callback('/\\w+/', (__callback__)new Replacer, "Hello, World!\n");

結果:

Hello, World!
Hello, World!
Array
(
    [0] => 9
    [1] => 4
    [2] => 8
    [3] => 2
    [4] => 5
    [5] => 1
    [6] => 6
    [7] => 10
    [8] => 3
    [9] => 7
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
    [9] => 10
)
Hello Hello, World World!