暖かくて通学中の自転車の上で眠ってしまいそうな今日この頃ですが、昨日のエントリで凄いミスを犯していたので訂正します。
というのも、昨日のバイトコード、引数をスタックに逆順で積まなければならない所を、見事にそのまま積んでいたので、コンストラクタには引数が全てひっくり返って渡されてしまっていました。正しいスクリプトはこちらになります。
__bytecode__("8E2E00637265617465496E7374616E636546726F6D537472696E67000200062A0001706174680002706172616D7300570096020004011C8701000317960A000402006C656E677468004e870100041796020004048701000517960200040551870100059605000700000000489d02000d00960400040204054e990200dbff9605000404040303533e");
見やすくするとこう。
function2 createInstanceFromString (r:1='path', r:2='params') ()
push r:path
getVariable
setRegister r:3
pop
push r:params, 'length'
getMember
setRegister r:4
pop
push r:4
setRegister r:5
pop
label1:
push r:5
decrement
setRegister r:5
push 0
lessThan
branchIfTrue label2
push r:params, r:5
getMember
branch label1
label2:
push r:4, r:3, UNDEF
newMethod
return
end // of function createInstanceFromString
うっかりしてましたスイマセン。
何かアクセスが増えてると思ったら、fladdict.netさんからトラックバックが来てた。スクリプトエンジンで(一部の)ビルトインクラスのnewが上手くいかないので汎用ファクトリを作ろうという話です。
何気なしに考えてたら、(あまり美しくは無いですが)バイトコード直書き(Cのインラインアセンブラみたいなの)で実現できることに気付いたので書いておきます。
まず、次のコードを1フレーム目とかに書いておきます。長いですが、区切ったり改行入れたりしないで下さい。
__bytecode__("8E2E00637265617465496E7374616E636546726F6D537472696E67000200062A0001706174680002706172616D73005B0096020004011C8701000317960A000402006C656E677468004e8701000417960500070000000087010005179604000405040448129d02001800960400040204054e9602000405508701000517990200daff9605000404040303533e");
これを書くと、createInstanceFromString()というメソッドが定義されます。ので、次のようにして使います。
import flash.display.BitmapData;
var b:BitmapData = createInstanceFromString("flash.display.BitmapData", [10, 10]);
第1引数にクラスのコンストラクタへのパスを文字列で、第2引数にコンストラクタに渡す引数を配列にして呼び出します(要素は何個でも大丈夫です)。すると、オブジェクトが生成されて返ってきます。以上。
実際どんなことをやっているか、flasmで見やすくしたバイトコードを載せておきます。
function2 createInstanceFromString (r:1='path', r:2='params') ()
push r:path
getVariable
setRegister r:3
pop
push r:params, 'length'
getMember
setRegister r:4
pop
push 0
setRegister r:5
pop
label1:
push r:5, r:4
lessThan
not
branchIfTrue label2
push r:params, r:5
getMember
push r:5
increment
setRegister r:5
pop
branch label1
label2:
push r:4, r:3, UNDEF
newMethod
return
end // of function createInstanceFromString
要は配列の要素を push してスタックに積んで、 newMethod を呼んでるだけです。
追記
上のコードバグってました。ストリングからクラスのインスタンスを作る 訂正をご覧下さい。
先日のスクリプトエンジンのVirtualMachine(VM)でも使っているテクニックなのですが、VMはバイトコードに対応した処理をしなければならないので、
for (; pc < length; ) {
switch (code[pc]) {
case 'NOP': pc += 1; break;
case 'LIT': code[code[pc+2]] = code[pc+1]; pc+=3; break;
case 'ADD': code[code[pc+3]] = code[pc+1] + code[pc+2]; pc+=3; break;
....
default: throw Error();
}
}
というように、大量のcase分岐が必要になります。Flashはswtich-caseで単純な比較&ジャンプのコードしか生成しないので、このように大量にcaseがあると、一番最後のcaseが実行される場合なんかは悲惨な速度になります。
そこで、このswitch-caseを次のようなメソッド呼び出しに変更することによって、効率化を図ることが出来ます。
for (; pc < length; ) {
pc += this[code[pc](code, pc);
}
...
private function NOP (code:Array, pc:Number) : Number
{
return 1;
}
private function LIT (code:Array, pc:Number) : Number
{
code[code[pc+2]] = code[pc+1];
return 3;
}
private function ADD (code:Array, pc:Number) : Number
{
code[code[pc+3]] = code[pc+1] + code[pc+2];
return 4;
}
....
勿論、メソッド呼び出しのコストはかかるのですが、caseが大量であれば、平均するとこちらのアプローチの方が効率的です。
もしこのようなコードを書く機会があれば、両方試してみて、最適なものを選んでみてください。
ActionScriptには、Function.applyというメソッドがあって、thisと任意長の引数を指定してメソッドを呼び出すことが出来ます。
function sum (a:Number, b:Number) : Number
{
return a + b;
}
var n:Number = sum.apply(null, [1, 2]);
// var n:Number = sum(1, 2); と同じ
この、「任意長の引数」というのがポイントで、どんなメソッドであろうが、何個引数があろうが呼び出せるので便利なのです。
んで、今回考えるのは、この「任意長の引数」をnewの時に実現できないか?という話。イメージとしては、java.lang.reflect.Constructor#newInstaceのような、Function.newInstanceというメソッドがあって、
var m:Matrix = Matrix.newInstance([a, b, c, d, tx, ty]); // var m:Matrix = new Matrix(a, b, c, d, tx, ty); と同じ
という感じで、任意個の引数でクラスを生成できるというもの。
これ、なかなか難しい問題なのですが、Days on the moonさんの、JavaScript の new 演算子の意味というエントリが、そのものずばりな内容になっています。つまり、ECMAScriptが定義する動作をシミュレートしてやればいいのです。
ちょこちょこ弄って、最終的にActionScriptで最も良いと思われるコードはこんな感じになりました。(先程のFunction.newInstanceの実装)
function _constructor () : Void
{
}
_global.Function.prototype.newInstance = function (args:Array) : Object
{
// prototypeの設定(継承)
_constructor.prototype = this.prototype;
// オブジェクトの生成
var instance:Object = new _constructor();
// instanceをthisとしてコンストラクタを呼び出す
// その時、コンストラクタが値を返せばそれを返し、
// そうでなければinstanceを返す
return this.apply(instance, args) || instance;
}
ECMAScriptにおける手順7.にあたる、(instance instanceof Object)のチェックを無くしているのは、プリミティブ型(String, Numberなど)のコンストラクタを呼んだ時に返ってくる値をそのまま返したほうが都合がよさそうだからです。
チェックあり(つまり、instanceを返してしまう)だと、例えば
var str:String = String.newInstance("hoge");
trace(str);
というコードで、出力が、
[type Object]
になってしまいます。
ちなみに、何でこんなメソッド(Function.newInstance)の話題が出てきたかというと、今作ってるプログラムでこれがどうしても必要だったからだったり...そのプログラムは近々公開すると思うので待っててネ
変数objがnullであれば、デフォルト値を返すコード、
return obj ? obj : 'default';
ってよく考えたら
return obj || 'default';
だよね。多分こっちの方が速いし。もしかして常識ですか。
・・・これだけでエントリが終わったらツマランので、ASで遅延評価メソッドを作るメソッドを考えてみよう。
昨日の続きと言えば続き。
やはり組み込みのクラスを拡張するのは比較的一般的みたいで、最速インターフェース研究会さんでは、JavaScriptで色々とごにょごにょした軌跡が掲載されてます。JavaScriptで使えるテクニックは、ほぼそのままActionScript1.0/2.0に適用できるので、読んでみる価値アリです。
で、更にこのBlogで、Ruby.jsという、Ruby的なメソッドをJavaScriptで実装しようという、まさに昨日俺が書いたような事をやってるプロジェクトが紹介されてました。こちらの方が網羅率が高いですね。
ただ、ブロック内からbreak;をする方法は思いつかなかったようで、フラグの状況でループを飛ばしてたりしてました。そこはなんとなく自己満足に浸る(まあ、あの方法もあんまり良いとは思えませんけど)。
最後に、読んでいてオォーと感動したテクニックを、ActionScriptで。
var list:Array = [1, 2, 3, 4]; // 配列のコピー var listCopy:Array = Array.apply(null, list);
new Array(1, 2, 3, 4);を呼び出すのと同等の効果が得られるという、まさに神業。
追記:そういえば前に話題になってたなぁ、と思ってprototype.jsを見てみたら、Enumerableなどに加えて、しっかりと例外を使ったbreak/continueが実装されていました。完全に先を越されてました。とほほ。
ブロック付きメソッド呼び出しとは、Rubyにある機能で、簡単に言えば関数自体を引数に渡してメソッドを呼び出しちゃうものです。(一般にはlambda式とかクロージャとか言われてる?)
[1,2,3,4].each {|item|
print item
}
これで、1~4が表示されます。Rubyやforeach的なモノを知らない人にはちょっと分かりにくいでしょうか。{}で囲まれた部分がブロック(関数)で、||で囲まれた部分が引数です。.eachメソッドを呼び出すと、各要素毎に、要素を引数として、ブロックが呼び出されるのです。
ちなみに、PHPで書けばこうです。
foreach(array(1, 2, 3, 4) as $item)
{
echo $item;
}
さて、ここまで書いて何が言いたいか分かった人も結構いると思います。
ブロック付きメソッド呼び出しは、ActionScriptでも出来るのです。
もしかしたら既にあるかもしれないけど、Reference型とういのを考えたので書いておく。(もっと頭いい方法があったら教えてください)
そういう状況自体あまりない気もするんだけど、Flashでどうしてもプリミティブ型を参照渡ししたい場合に、このReference型は役立ちます。以下のコードのようなのクラス。
class Reference
{
public var value:Object;
public function Reference (value:Object) { this.value = value }
public function valueOf () : Object { return value.valueOf(); }
public function toString () : String { return value.toString(); }
}
このクラスを使うと得られる利点は、「値の読み取りならvalueプロパティを介さずに行える」というコト。以下、いくつか例を挙げます。
数値の場合。
// 参照渡しされたデータを変更するメソッド
function change (ref:Reference) : Void { ref.value = 3; }
var ref:Reference = new Reference(1);
trace(ref); // 1
change(ref);
trace(ref); // 3 ←変更されている
trace(ref+2); // 5 ←数値として計算できている
文字列も同じように出来るので省略。論理型の場合だけ少し工夫が必要で、明示的にtrueと等しいか比べてやることで正しく値を読み取ることが出来ます。
// 参照渡しされたデータを変更するメソッド
function change (ref:Reference) : Void { ref.value = false; }
var ref:Reference = new Reference(true);
if(ref==true) { trace("true"); } else { trace("false"); } // true
change(ref);
if(ref==true) { trace("true"); } else { trace("false"); } // false
勿論値の設定をする場合はvalueプロパティを介さないとダメです。



