PHPカンファレンス2008でPHP5.3の無名関数を試してみた

in

PHPカンファレンス2008に行ったらamachang(初対面)が、「PHPはλが使えないのがな~」と言うと、森川CTO(初対面)に「そんなの要らないじゃん」などと絡まれていました。
10月にはリリース予定のPHP5.3で無名関数が追加されるそうなので、なんぼのもんか試してみました。
・・・これなら使える!?

まずPHP5.3の開発版ですが、公式サイトのSnapshot Buildsから簡単にダウンロードできます。
PHP5.2の欄ではなくてPHP5.3の欄なので注意。Windowsならzipを展開するだけです!
さっそく使ってみましょう。

$f = function () {
	echo 'Hello World';
};
$f();
// Hello World

実行の仕方はcreate_functionと似てますね。
代入文なので、}の後のセミコロンを忘れないようにしましょう。

ちなみに無名関数を代入した変数をprint_rすると、Closure Object()とかでてきます。
create_functionの関数には lambda_1 のような文字列が割り当てられるので、内部的にはだいぶ違う作りになっていることがわかります。
また、無名関数内でvar_dump(__FUNCTION__);とすると、空文字列です。無名だから空なのでしょうか。

ここまでだと書きやすいのがメリットというだけにも見えるかもしれませんが、色々なパターンを試してみます。

$i = 2;
$f = function ($j) use ($i) {
	echo $j * $i;
};
$f(3);
// 6

useに変数名を含めると、関数内から参照できます。
JavaScriptの場合はデフォルトで参照できますが、PHPでは指定した変数のみです。
これは面倒かも知れませんが、実際に無名関数を通常の関数やメソッドに移動させる場合(しばしばある)などを考えるとありかもしれません。

これをcreate_functionで書くとすると、次のようになるので、単純な関数以外を書く気が失せるかと思います。

$i = 2;
// 引数を増やす
$f = create_function('$j, $i', 'echo $j * $i;');
$f(3, $i);

$i = 2;
// 文字列部で合成
$f = create_function('$j', 'echo $j * ' . $i . ';');
$f(3);

JavaScriptと違い、無名関数を作ったそばから、変数に代入せずに呼び出すことは、残念ながらできません。
例えば、下記はどちらも Parse error: syntax error, unexpected '(' となります。
また、defineで定数に割り当てることもできません。

$i = 2;
function ($j) use ($i) {
	echo $j * $i;
}(3);

$i = 2;
(function ($j) use ($i) {
	echo $j * $i;
})(3);

useは関数の呼び出し位置ではなく、宣言位置での変数値が参照されます。下記では、5ではなく、2が使われます。

$i = 2; // used
function f($j, $fn) {
	$i = 5; // not used
	$fn($j);
}
$fn = function($j) use ($i){
	echo $j * $i;
};
f(3, $fn);

次のような場合、7,2、5,2、3,2の順に表示されます。$aは宣言時にコピーされており、関数が終われば破棄されています。

$a = array('a'=>7,'b'=>2);
$f = function() use ($a){
	print_r($a); //7,2
	$a['a']=5;
	print_r($a); //5,2
};
$a = array('a'=>3,'b'=>2);
$f($a);
print_r($a); //3.2

引数と同様、use内で&をつけることで、コピーさせず、配列の変更を呼び出し元に反映させることができます。
この場合、3,2、5.2、5.2と表示されます。
参照を保持しているので、7.2ではなく、呼び出し時の値3.2が使われるのと、呼び出し後に5.2のままなことが確認できます。

$a = array('a'=>7,'b'=>2);
$f = function() use (&$a){
	print_r($a); //3,2
	$a['a']=5;
	print_r($a); //5,2
};
$a = array('a'=>3,'b'=>2);
$f($a);
print_r($a); //5.2

再帰呼び出しも可能です。PHPではいま実行中の関数名を取得することができないし、__FUNCTION__も空文字列なので、
useに&をつけて、代入しようとする変数自身を設定すると動作します。

$f = function($i) use (&$f){
	return $i ? $i + $f($i - 1) : 0;
};
echo $f(10); //55

use ($f) にしてしまうと、再帰呼び出しのところで、Fatal error: Function name must be a string となります。
ここで&$f使えるのはおかしい気がしないでもないですが、心配なら一行上で$f = null;と書いておけば確実でしょう。
なお、関数内で$f = null;などとすると、Fatal error: Cannot destroy active lambda function となります。

さて、次は無名関数同士で呼び合うサンプルです。下のようにすると、$hにも関数が割り当てられるので、ちゃんと!と?が交互に使われます。

function f($i){
	$h = null;
	$g = function($i) use (&$h){
		echo $i, "!\n";
		if (!$i) return;
		$h($i-1);
	};
	$h = function($i) use (&$g){
		echo $i, "?\n";
		if (!$i) return;
		$g($i-1);
	};
	$g($i);
}
f(10);

ちょっとこれ別の書き方してみましょうか・・・あれ?
動くよこれ!

function f($i, $g, $h){
	$g($i, $g, $h);
}
f(10, function($i, $g, $h) {
	echo $i, "!\n";
	if (!$i) return;
	$h($i-1, $g, $h);
}, function($i, $g, $h) {
	echo $i, "?\n";
	if (!$i) return;
	$g($i-1, $g, $h);
});

無名関数は引数に渡せるようです!また、&は不要のようですね。
とすると次に試したいのはもちろん・・・。

function f($i){
	return function($j) use($i) {
		return $i * $j;
	};
}
$f = f(3);
echo $f(2), "\n"; // 6
echo $f(4), "\n"; // 12

ついにPHPでも関数が一級(ファーストクラス)オブジェクトになったか?!
しかし、$f(3)(4)や($f(3))(4)は、またしても Parse error: syntax error, unexpected '(', expecting ',' or ';' なのは残念。

最後にJavaScriptのカリー関数のサンプルをPHPに書き直してみましたよ!

// http://wiki.livedoor.jp/mahalkita/d/Javascript%A4%C7%B9%E2%B3%AC%B4%D8%BF%F4
function curry($func_name, $func_num_args){
	$args = array();
	$sub_curry = function() use (&$args, &$sub_curry, &$func_name, $func_num_args) {

		$args = array_merge($args, func_get_args());
		if (count($args) >= $func_num_args)
			return call_user_func_array($func_name, $args);
		else
			return $sub_curry;
	};
	return $sub_curry;
}
function mean3($a, $b, $c){
	return ($a + $b + $c) / 3;
}
$g = curry('mean3', 3);
$h = $g(100, 20);
echo $h(30); // 50
echo $g(30, 100, 20); // 50

結構使えそうなので、JavaScriptに相性が良いのはPHPってことで、
amachangがPHPを気に入ってくれると良いな~と思います。(結論それ?)

カンファレンス全体の感想は追って。

ちなみにドキュメントはここかこのcvs版辺りにあるべきと思うのですが、今のところまだ見当たりません。Introducing PHP5.3にも載ってないし。

PHP5.3無名関数シリーズ2はこちら

Trackback URL for this post:

http://nonn-et-twk.net/twk/trackback/223
from twk @ ふらっと on 火, 2008-07-22 10:20

PHP5.3の無名関数がそこそこ使えそうだったので追加調査。

まずはいままで関数名を渡していたcallback引数に無名関数を使えるか。array_walkとpreg_replace_callbackのサンプルから変換してみます。

from slowbirds.scraps on 火, 2008-07-22 08:56

$f = function () { echo 'Hello Wo...

0