BeInteractive!

前のエントリの続きです。

で、たまたま見つけたんですが、どうやら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クラスで)

DL [zip]

ちなみに、発展させると次のような変態行為も可能なので、是非考えてみてください。

var n:Number = 10;
// 10回ループ
n.loop(function(i)
{
 trace(i*2);
});

この記事へのトラックバック

buy levitra
Wie Ihren Artikel zu Digg inzuzufügen?

TrackBack URL:

http://www.be-interactive.org/trackback.php?id=39

この記事へのコメント

ホゲ wrote:
ファイルダウンロードさせて頂きました。
ありがとうございます!

質問なのですが、
main.asなどで
import util.Mixin;
としていますが、
utilパッケージが見あたらなかったので、
Mixin.asなどは別のページからダウンロードすればいいでしょうか?

どのようにMix-inを実現しているのか、非常に知りたいのです。

なんだか的外れな事を質問しているかもしれませんが
よろしくお願いします!
yossy wrote:
すいませんすいません。見事にzipし忘れていました。
上げ直したので再ダウンロードして頂ければと思います。

1年以上この状態だったとは・・・恐るべし。
ホゲ wrote:
再UPありがとうございます!!
ダウンロードしました!

中見るのたのしみです!

コメント書き込み:

カテゴリ

タグ

アーカイブ