ActionScript3のarguments.calleeのサンプルコードがひどい
FlashのAPIドキュメントは前からサプルコードの質がひどいのはわかっていたけど、こんなにわかりやすいダメっぷりは新記録なので取り上げます。
http://livedocs.adobe.com/flex/3_jp/langref/arguments.html#callee
ECMAスクリプト使いにはお馴染み、argumentsのcalleeに関してですが。
現在実行中の関数への参照です。
説明それだけ?うーん、サンプルコードがいろいろ物語っているのかなと思いきや…
次のコードは、secondFunction() という名前の関数を呼び出す関数への参照を取得します。firstFunction() 関数は、ブール値引数 true を持ち、secondFunction() が firstFunction() を正常に呼び出していることを示して、各関数が他方の関数を相互に呼び出すことによる無限ループを防ぎます。
callSecond パラメータが true であるため、firstFunction() は secondFunction() を呼び出し、自身への参照を唯一の引数として渡します。関数 secondFunction() は、データ型が Function で、caller という名前のパラメータを使用して、この引数を受け取り、保存します。次に、secondFunction() 内から、caller パラメータで firstFunction 関数を呼び出します。ただし、このとき callSecond 引数を false に設定します。
実行が firstFunction() に戻ると、trace() ステートメントが実行されます。これは、callSecond が false であるためです。
package { import flash.display.Sprite; public class ArgumentsExample extends Sprite { private var count:int = 1; public function ArgumentsExample() { firstFunction(true); } public function firstFunction(callSecond:Boolean) { trace(count + ": firstFunction"); if(callSecond) { secondFunction(arguments.callee); } else { trace("CALLS STOPPED"); } } public function secondFunction(caller:Function) { trace(count + ": secondFunction\n"); count++; caller(false); } } }
いや、だからそれ、calleeの話ちゃうやん。
calleeの説明そっちのけで、AS3にcallerがないのをどうやって補うかと、無限再帰を止めるためにどうするかを説明している。んで、calleeって何の役に立つのかが全く意味不明。これだったら、いつものように、trace(arguments.callee) って書いてあるほうがまだマシ。
ちなみに、サンプル中のarguments.calleeのとこ、firstFunctionって書いてもよくて、むしろそっちのほうがメモリ管理がオブジェクト単位になるから、できるならそうしたほうが安全かも。初期のAVM2は、オブジェクトが死んでクロージャだけ生きてると、ガベゴレの余裕がないぐらい高負荷がかかったとき、ブラウザごとクラッシュすることがありました。
どれ、ちゃんと説明してみるか。
callee プロパティ
このフィールドは、呼び出された関数自身を指すオブジェクトを参照しています。calleeは、C言語の関数呼び出しメカニズムに例えると、呼び出す関数へのポインタを格納したレジスタのようなものです。
ActionScriptの構文はJavaに似ていますが、実効モデルはむしろ、いまだJavaScriptに似ています。ActionScriptにおける関数は、かならずしもメソッドとしてオブジェクトに独占されるものではなく、オブジェクトと関数はそれぞれ分離して管理されます。(ただし、AVM2ランタイムではJavaモデルが最も上手く動くよう最適化されています(…と思ってるけど、うそかな))
オブジェクトのフィールドとして名前を持つメソッド、または、明示的に名前を付けて管理されるローカル関数の場合、その関数実装の内部からは、かならず、(thisスコープかレキシカルスコープに関数の定義が見えるので)名前を根拠に関数自身の参照を得ることができます。コンパイラが構文を静的解析することが可能になることから、通常は、この固定的な名前を用いた関数の参照が推奨されます。
しかし、関数リテラル構文によって無名関数が書かれた場合、その実装の内側から、固定的な名前によって自己参照する手段が絶たれます。このような場合、calleeを参照することが、唯一の有効な手段となります。
例
ArgumentsExampleクラスのdoLater関数は、特定の処理を次回の描画更新後まで遅らせるユーティリティ関数です。タイマーによる遅延ではなく、ENTER_FRAMEを用いるため、確実にひとつ後の描画更新直後に処理を実行できます。
この例では、引数に渡された呼び出しコンテキストを保存するため、クロージャを無名関数で実現しています。目的の処理を実行する前に、この無名関数がイベントリスナのリストから自己を削除している部分に注目してください。
package { import flash.display.Sprite; import flash.events.Event; public class ArgumentsExample extends Sprite { public function ArgumentsExample() { trace("1"); doLater(function():void { trace("2"); }, this); trace("3"); /* ... will be shown 1,3 and 2(after 1f). */ } private function doLater(laterProc:Function, obj:Object=null):void { addEventListener(Event.ENTER_FRAME, function(event:Event):void { removeEventListener(Event.ENTER_FRAME, arguments.callee); laterProc.call(obj); }); } } }
もちろん、この無名関数をいったんdoLaterのローカル変数に格納することも可能ですし、そのほうがわかりやすくなる場合もあります。場合によりますが、この例のようなコードを書くメリットとして、引数に見えているもの以外に、シンボルテーブルを消費する変数がないということ、本来名前がないものに、構文の都合のせいで名前を与えることを強いられずに済むということ、などが考えられます。
どうだろう?高度すぎる?でも待って、これはたぶん、calleeを初めて使うとき、もっとも現実的なシチュエーションです。なので、扱う対象に対して、十分に入門的だと思いますよ。
本質的に高度なものを語るためには、その高度さの一歩手前までを、当然の前提としなきゃ話にならない。より簡単な説明、より低級な課題の消化で、わかった気にさせてしまおうというごまかし行為は、教育上とても危険なことだと思う。
そうそう、Javaスクールでポインタと再帰を教えないから困ると、ジョエルさんも言ってましたっけ、たしか。
いくら経営者がこれで十分と言ったとしても、プログラマーのボスはこんなひどいもの(Adobe Labsの恥さらしになるうえ、せっかく土俵を変えたのに、またマイナスのユーザ教育をしてしまう)、決して見過ごしてはいけなかったんじゃないかな。