前のエントリの続きです。
で、たまたま見つけたんですが、どうやらFirefoxでは、JavaScript1.5辺りから、Array.forEachメソッドという、瓜二つなメソッドが実装されてるぽいです。世の中考えることは皆同じですネ...
ただ、とりあえずまだ書くことがあるので書いておきます(また誰かが先を越してるかもしれないけど・・)。
まず、continueについて。通常のループでは、continueを使うと残りの処理を飛ばして、次のループ処理をします。
for(var i:Number=0; i<list.length; i++)
{
var item = list[i];
if(item < 5) continue;
trace(item);
}
上の例では、要素が5未満であれば、traceをせずにスキップをします。
では、これをeachに適用するとどうなるでしょうか。答えは次の通りです。
list.each(function(item)
{
if(item < 5) return;
trace(item);
});
returnします。実体はメソッドなので、当然returnをすれば、残りの処理はスキップされます。そして、うまい具合に次のループへ入ると。ちょっと直感的では無いのがナントモですが、仕方ないです。
では、次はmemberメソッドを実装する事を考えて見ましょう。memberメソッドは、単純な線形探索で、要素の中に指定された値が含まれていればtrueを返します。通常のループで書くとこうです。
function member (value:Object) : Boolean
{
var result:Boolean = false;
for(var i:Number=0; i<list.length; i++)
{
var item = list[i];
if(item==value)
{
result = true;
break;
}
}
return result;
}
簡単ですね。要素を比較して一致するものがあればフラグを更新して、ループから脱出します。(※見つけたら即return true;の方が当然いい訳ですが、この後のためにあえて冗長な書き方をしています。)
では、これをeachメソッドを使って書いて見ましょう。・・・ここで、あれ?と思った人は正しいです。どういう事かというと、eachメソッドにおいて、「ループの脱出」にあたる手段が無いのです。returnしようがbreakしようがcontinueしようが、ダメです。
function member (value:Object) : Boolean
{
var result:Boolean = false;
list.each(function(item)
{
if(item==value)
{
result = true;
// どうやって脱出すんの?
}
}
return result;
}
これは困りました。returnする値でループの継続を判断するのも良さそうですが、return値が意味を持つ場合もあるので(collect, findなど)汎用的ではありません。
さてさてどうしたものかと考えると、実はうまく解決する手段があります。例外処理です。ただし、例外の本来の使い方ではないので良くありません。ただ、次に示すクラスによって例外は隠蔽されるのでまあ良いか・・・とか(どっちだよ)。
class util.Closure
{
private static var instance:Closure = new Closure();
private function Closure ()
{
}
public static function breakClosure () : Void
{
throw instance;
}
public static function execute (closure:Function) : Void
{
try
{
closure();
}
catch(e:util.Closure)
{
}
catch(e)
{
throw e;
}
}
}
というクラスを用意しておいて、Array.eachメソッドを次のように書き換えます。
Array.prototype.each = function (closure:Function) : Void
{
var len:Number = this.length;
var a:Array = this;
Closure.execute(function()
{
for(var i:Number=0; i<len; i++)
{
closure(a[i]);
}
});
}
なにやら先程のクラスClosure.executeメソッドを呼び出していますが、後で解説します。
次に、memberメソッドの問題の部分を次のように書き換えます。
function member (value:Object) : Boolean
{
var result:Boolean = false;
list.each(function(item)
{
if(item==value)
{
result = true;
Closure.breakClosure(); // 脱出!
}
}
return result;
}
これでうまくいくのです。item==valueになった瞬間、eachから抜けてreturn result;に移行します。
仕組みですが、まあ解説するほどのものでもないのですが、Closure.executeメソッドが呼ばれると、tryブロックの中で引数に渡されたメソッドが実行されます。そして、Closure.breakClosureメソッドを呼ぶと、例外Closureがスローされるので、うまい具合にexecuteメソッドがそれをキャッチしてくれて、メソッドを抜けられるという訳です。(それ以外の例外はそのままスローされます)
それでは最後に折角なので、このClosureクラスと、組み込みクラスにeach等のメソッドを埋め込むためのクラス(Mix-in)と、ArrayにRubyのEnumerableっぽいものを付けるサンプルを加えて置いておきます。
import util.Mixin;
// Mix-in
Mixin.mix(Array, ArrayModule);
Mixin.mix(Array, EnumerableModule);
// 1~5を要素に持つ配列を作る
var list:Array = Array.create(5, function(index)
{
return index+1;
});
// 出力してみる
list.each(function(item)
{
trace(item);
});
こんな感じで使えます。詳しくは同梱のmain.asとかクラスファイルを参照してください。
これには同梱していませんが、Mix-inを使うと、当然「そんなメソッド無い」とコンパイラに怒られるので(Arrayはdynamicクラスなので出ないですが)、flaファイルと同じ場所に、Array.asだとかNumber.asだとか、Classesフォルダからコピーしてきて該当メソッドの宣言を加えないといけません。(勿論、intrinsicクラスで)
ちなみに、発展させると次のような変態行為も可能なので、是非考えてみてください。
var n:Number = 10;
// 10回ループ
n.loop(function(i)
{
trace(i*2);
});
サイト内関連記事
この記事へのトラックバック
TrackBack URL:
http://www.be-interactive.org/trackback.php?id=39

ありがとうございます!
質問なのですが、
main.asなどで
import util.Mixin;
としていますが、
utilパッケージが見あたらなかったので、
Mixin.asなどは別のページからダウンロードすればいいでしょうか?
どのようにMix-inを実現しているのか、非常に知りたいのです。
なんだか的外れな事を質問しているかもしれませんが
よろしくお願いします!
上げ直したので再ダウンロードして頂ければと思います。
1年以上この状態だったとは・・・恐るべし。
ダウンロードしました!
中見るのたのしみです!