PHP5.3未満で無名関数のようなものを書けないか足掻いてみた
あそびです。
無名関数というか、Rubyでいうブロックのようなものを引数にとれるようにしたくて、pure PHPだけでどこまでできるか色々と試行錯誤してみました。何に使うのかはあまり考えてません。
実際にできたのは↓のような書き方の何かです。見た目は気持ち悪いですが、そこそこ時間をかけてしまったので公開しつつ色々と補足します。
require 'bloc.php'; $hoge = 10; $b = bloc();if(called){ $hoge++; return $hoge; }; $b->call(); # => 11 $b->call(); # => 12 $b->call(); # => 13
$bに代入している行がどうみても不自然な感じがしますが、if(called){から};までの間は、すぐに評価されずに後でcall()を通して呼ぶことで何度でも呼び出すことができます。
仮にこれを「ブロック」とか呼んでおきます。ブロック内のローカル変数の状態は保持されます。ブロック外のグローバル変数はブロック内でも普通に参照できます。
ただ、メソッド(と関数)の中などのローカル変数はブロック内からは直接参照できないので、bloc()の引数にハッシュで渡す必要があります。だけどそうするともはやわけがわからなくなる。
function foo(){ $hoge = 20; $b = bloc(array('hoge'=>$hoge));if(called){ $hoge++; return $hoge; }; echo $b->call(); # => 11 echo $b->call(); # => 12 echo $b->call(); # => 13 } foo();
ブロック内からは外にある変数を破壊することができません。それから、ブロック内で定義した変数は外では直接は参照できません。->でブロック内のローカル変数をプロパティみたいに参照することはできます。
$hoge = 'piyo'; $b = bloc();if(called){ $hoge = 'fuga'; $foo = 'bar'; return $foo; }; echo $b->call(); # 'bar' と表示 echo $hoge; # 'piyo' と表示 echo $foo; # 何も表示されない echo $b->foo; # これなら 'bar' と表示
何をやっているかというと、普通に走らせたら絶対に通らないようにした部分をbacktrace情報を頼りにどうにか抽出して、呼ばれるたびにevalしてます。ただ直接eval書いているだけだと面白くないし、書いているときに文字列扱いになってエディタのシンタックスハイライトの恩恵を受けられないし、evalする前と後で変数をすり替えるなどの処理を入れたかったのでこうなりました。
実は、PHP5.3の無名関数の動きをよく調べずに書いたので色々違うと思います。いつかちゃんと調べます。
今の時点での目立った問題点は4つ。
- ネストできない
- ブロック外のローカル変数を拾うのに手間がかかる
- エラー処理してない
- すごく遅い
どこかバグってそうですし実用には全く向きませんが、おもしろ半分に作ったら割と面白かったのでgistに上げています。(gist初めて使った。べんり!)