Ruby 的ブロック

昨日の NEWZingo の話からの発展です。

大した意味は無いんですが、News cloud を表示した時に、自動的に最初のタグ (タグクラウドの意味でのタグです) にフォーカスを置くようにしたいと思ったんです。で、探すと focus というメソッドが見つかりました。

さらに、最初のタグではなくて、最初の最も大きなタグにフォーカスを置くようにしてみよう、と思い付きました。こんなコードになりました:

    find_biggest: function ()
    {
	var biggest = '0';
	var tags  = {};
	var links = document.all.tags('A');

	for (var i = 0; i < links.length; i++)
	    // タグのクラス名は "tag0" .. "tag7" となっている。その数字を得る
	    if (links[i].className.match(/^tag(\d+)$/))
	    {
		var num = RegExp.$1;
		if (tags[num])
		    continue;	// タグクラウド全体をキャッシュする必要は無いので
		else
		{
		    tags[num] = links[i];
		    // 最大値を更新
		    if (parseInt(num) > parseInt(biggest))
			biggest = num;
		}
	    }
	return tags[biggest];
    }

ハッシュ tags は、例えばこんな感じになります:

tags[2]: http://newzingo.com/US/tag/adobe
tags[0]: http://newzingo.com/US/tag/afghanistan
tags[5]: http://newzingo.com/US/tag/bird
tags[3]: http://newzingo.com/US/tag/coast
tags[7]: http://newzingo.com/US/tag/sharon
tags[4]: http://newzingo.com/US/tag/stars

タグの各サイズにつき、最初に出くわしたアンカーが格納されているわけです。そして、変数 biggest は最も大きいサイズの値を持つようになっていますので、このメソッドは tags[7] を返します。したがって、呼び出し元でそのまま focus() を適用すれば期待の動作になります。

ここで、最小のタグを返すメソッドも作りたい、と考えるのが人情ですよね。

ところが、書こうとすると、重複する部分があまりにも多いことに気付きます。変更が必要なのはタグのサイズの比較部分だけなんです。

そこで、一般化できないか考えてみました。このようになりました:

    find_biggest: function ()
    {
	return this.find_tag(function (a, b){ return a > b; });
    },
    find_smallest: function ()
    {
	return this.find_tag(function (a, b){ return a < b; });
    },
    find_tag: function (cmp)
    {
	var scale = '0';
	var tags  = {};
	var links = document.all.tags('A');

	for (var i = 0; i < links.length; i++)
	    if (links[i].className.match(/^tag(\d+)$/))
	    {
		var num = RegExp.$1;
		if (tags[num])
		    continue;
		else
		{
		    tags[num] = links[i];
		    if (cmp(parseInt(num), parseInt(scale)))
			scale = num;
		}
	    }
	return tags[scale];
    }

find_tag の中身は、前の find_biggest と殆ど同じです。違うのは変数名と、比較部分を関数呼び出しとしている点だけです。Ruby の、ブロックをメソッドの引数として取る機能と同様のものを、無名関数で実現できるという一例でした。