時代遅れひとりFizzBuzz祭り JScript on WSH編

時代遅れひとりFizzBuzz祭り、今回はJScript on WSH。前々回のPowerShellからの流れで、バッチファイルの後継繋がりでWSHを選んだとも言えるし、前回のREXX共々「バッチファイルの代用」という側面*1で共通性があるとも言える。もっともWSHはあまり広まることなく廃れる一方で、しかも今後はWindows PowerShellに移行していくはずなので、元々広くなかった活躍の場が更に狭くなりそうだ。

それでも個人的にはWSHは意外とオススメだと思う。特にプログラマ向けにJScriptなんてどうだろう?

理由:

  • JScriptが使えるということは、要するにJavaScript(の親戚)でWindows上で動く小ツールを書けるということだ。JavaScriptは結構強力な言語だ。無名関数とかクロージャとか、存分に使うべし!
  • HTA(HTML Application)と組み合わせることで、クライアントサイドJavaScriptの要領で簡易GUIアプリを実装できる。

COMコンポーネント周りを除けば、JScriptHTAによる開発ではJavaScriptの知識の大部分を流用できる。JavaScriptについてはオライリーの『JavaScript 第5版』という良書があるし、WSHの基本的なコンポーネントについてはネット上で情報収集すれば事足りる*2。まあその他のCOMコンポーネントの情報が探しにくいのが辛いところではあるが。

あと他人に使ってもらうツールを作る場合に、現状ではPowerShellよりもWSHの方が使ってもらいやすいという面があるのは否定できなかったりする。Windows XPVistaに最初から入っている、という面は大きい。

JScriptは一応ECMAScriptの実装というか方言というかそんな感じなので、極力ECMAScript由来の機能に絞り込んでFizzBuzzを書いてみた。動作環境はWindows XP Professional SP3上のWSH 5.7。

(function() {
	var print = function(s) {
		WScript.Echo(s);
	};

	var fizzbuzz = function(n) {
		var fb_str = [
			n%3 === 0 ? 'Fizz' : '',
			n%5 === 0 ? 'Buzz' : '',
		].join('');

		return fb_str.length === 0 ? n : fb_str;
	};

	var i;

	for (i = 1; i <= 100; ++i) {
		print(fizzbuzz(i));
	}
})();

意味はないけど配列で文字列を連結してみた。大量の文字列や文字列以外のオブジェクトを多く含む時は+演算子よりも配列で連結したほうが効率的らしいが、この程度では効果は無いはずだ。

私は流されやすい性格らしい。以前読んだ『JavaScript: The Good Parts』の影響を受けた書き方をしていると思う。

(function() {
	var print = function(s) {
		WScript.Echo(s);
	};

	var fizzbuzz = function(n) {
		var ref_tbl = [n, 'Fizz', 'Buzz', 'FizzBuzz'];
		var fizz = (n%3 === 0) ? 1 : 0;
		var buzz = (n%5 === 0) ? 2 : 0;

		return ref_tbl[fizz + buzz];
	};

	var i;

	for (i = 1; i <= 100; ++i) {
		print(fizzbuzz(i));
	}
})();

ECMAScriptの標準オブジェクトはそれなりに機能が揃っているが、他のLLに比べるとコンパクトな印象を受ける。個人的には、Rubyを齧っている為かArrayクラスの機能が不足しているように感じるし、Stringクラスもどこか物足りない。

prototype.jsを使えると良いのだが、WSHでそのまま組み込んで使うことはできないのが残念だ*3

このあたりはMozillaでも物足りなさを感じているのか、JavaScript 1.6以降などで幾つかメソッドが追加されている。その中でも一番欲しいのがArray.forEachメソッドだ。

(function() {
	var print = function(s) {
		WScript.Echo(s);
	};

	if (Array.prototype.forEach === undefined) {
		// MozillaのJavaScript 1.6の拡張機能であるArray.forEachと同等のメソッド。
		Array.prototype.forEach = function(fn, that) {
			var i, len = this.length;

			for (i = 0; i < len; ++i) {
				if (i in this) {
					fn.call(that, this[i], i, this);
				}
			}
		};
	}

	var fizzbuzz = function(n) {
		var ref_tbl = [n, 'Fizz', 'Buzz', 'FizzBuzz'];
		var fizz = (n%3 === 0) ? 1 : 0;
		var buzz = (n%5 === 0) ? 2 : 0;

		return ref_tbl[fizz + buzz];
	};

	var make_array = function(n, fn) {
		var i, ary = [];

		for (i = 0; i < n; ++i) {
			ary.push(fn(i));
		}
		return ary;
	};

	make_array(100, function(n) { return fizzbuzz(n+1); }).forEach(print);
})();

ただ、実のところJavaScript 1.6のArray.forEachはthisを引数に取れるようになっている点で微妙にインターフェースがゴチャゴチャしている感じがして、あまり好きになれない。それでもあれば非常に助かる。

Array.forEachが定義してあると、それを使って配列を舐める類の他のメソッドを幾つか定義できる。別にArray.forEachを使用しなくても定義できるのだが、何となく似たような内容を書くことになり、なぜか損した気分になってしまう。

(function() {
	var print = function(s) {
		WScript.Echo(s);
	};

	if (Array.prototype.forEach === undefined) {
		// MozillaのJavaScript 1.6の拡張機能であるArray.forEachと同等のメソッド。
		Array.prototype.forEach = function(fn, that) {
			var i, len = this.length;

			for (i = 0; i < len; ++i) {
				if (i in this) {
					fn.call(that, this[i], i, this);
				}
			}
		};
	}

	if (Array.prototype.map === undefined) {
		// MozillaのJavaScript 1.6の拡張機能であるArray.mapと同等のメソッド。
		Array.prototype.map = function(fn, that) {
			var ary = [], thisp = this;

			this.forEach(function(val, i) {
				ary.push(fn.call(that, val, i, thisp));
			});
			return ary;
		};
	}

	var fizzbuzz = function(n) {
		var ref_tbl = [n, 'Fizz', 'Buzz', 'FizzBuzz'];
		var fizz = (n%3 === 0) ? 1 : 0;
		var buzz = (n%5 === 0) ? 2 : 0;

		return ref_tbl[fizz + buzz];
	};

	var make_array = function(n, fn) {
		if (fn === undefined) {
			fn = function(i) { return i; };
		}
		var ary, i;

		if (n <= 0) {
			throw new RangeError();
		}
		ary = new Array(n);

		for (i = 0; i < ary.length; ++i) {
			ary[i] = fn(i);
		}
		return ary;
	};

	make_array(100).map(function(n) { return fizzbuzz(n+1); }).forEach(print);
})();

Array.forEachを使用してArray.mapを定義し、両方とも使ってみた。

本当は、最後の1行は、

	new Array(100).map(function(n) { return fizzbuzz(n+1); }).forEach(print);

のように記述したいのだが、Array.forEachやArray.mapは値が代入されているインデックスにのみコールバック関数を適用する仕様なので、「new Array(100)」で生成した直後の配列には使えない*4

JScript(特にWSHJScriptのように動的型のもの)は、個人的にはもっと流行して欲しい。JavaScriptは強力な言語なので、Web開発以外の分野にも普及してくれるとうれしいのだが、JScriptは方言とはいえWindows付属でLLに似た感じでツールを実装できる訳で、結構有力な候補だと思うのだ。しかし未だにマイナーだし、今後の普及の望みは薄い。残念だ。

*1:IBM PC-DOSOS/2にはREXXがバンドルされていて、バッチファイルの代わりに使われていた。WSHも、Microsoftの当初の予定ではバッチファイルの代わりになるはずだったのだが……。

*2:私みたいに『WSHクイックリファレンス 第2版』などを購入してもよい。

*3:元ネタを忘れたが、若干手直しすれば使えるらしい。

*4:生成した直後、配列のどのインデックスにも値が代入されていない為。