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

時代遅れひとりFizzBuzz祭り、今回はVBScript on WSH。前回のJScriptとの繋がりは、言うまでもないだろう*1

WSHの言語というと何故かVBScriptのイメージが強い。ネット上でWSHのサンプルを探すと大抵VBScriptで書かれている。それだけでなく、COMオブジェクト経由で色々な機能を使おうとした時にVBScriptでしかマトモに使えないことがある。以前Windows Server 2003 R2上でADSIを使ったJScriptスクリプトを動かそうとして、コレクションを舐めることができなくて挫折したことがあった。いや単にやり方が悪かっただけかもしれないけど、VBScriptのFor Each 〜 Nextで舐めるようなコレクションをJScriptでEnumeratorオブジェクト無しでどう舐めればよいのさ*2

VBScriptの言語自体の機能には意外と興味深い点がある。シンプルな文法*3、動的型、継承の無い単純なクラス機構、Eval。プロシージャをオブジェクトとして扱うこともできる*4。プリミティブなきらいはあるものの、組み込み関数もそれなりに一通り揃っている。そして何よりJScriptと同様にCOMベースのサービスを容易に扱うことができる。

ただ惜しいことにVBScriptの言語本体にフォーカスした資料というとMSDNのユーザーズガイドやリファレンスのような形式のものしか見つからないし、且つ数が少ない。他は「VBScript + WSH + COMでこんなことができる!」みたいな内容ばかりだ。

COMで公開されている便利な機能を使って色々なことを簡単に実現できるのは素晴らしいのだが、それに注目される余りに他の部分がおざなりにされている印象を受けるのだ。ネット上のVBScriptで書かれたサンプルコードには質が悪いものも多いのだが、VBScriptを本職の人ほど使っていない私から見ても「あの機能を使えばいいのに……」という感想を持つようなものだったりする。

VBScriptの現状について個人的に色々と思うところがあるのだが、それでもそれなりに有効な言語であることに変わりはない。前回のJScriptと同様に、Windows XP Professional SP3上のWSH 5.7で動かしてみた。

まずは関数を定義しつつも私なりに標準だと思われるスタイルで、オーソドックスなFizzBuzzを書いてみた。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private Function FizzBuzz(ByVal n)
	If n Mod 15 = 0 Then
		FizzBuzz = "FizzBuzz"
	ElseIf n Mod 3 = 0 Then
		FizzBuzz = "Fizz"
	ElseIf n Mod 5 = 0 Then
		FizzBuzz = "Buzz"
	Else
		FizzBuzz = CStr(n)
	End If
End Function

Private i
For i = 1 To 100
	Print FizzBuzz(i)
Next

「Option Explicit」は別として、PrivateやByValといったキーワードが使われているサンプルコードは余り見たことがない。クラスの外でPrivateで宣言した変数はそのスクリプト内でのみ参照できる、ということらしいのだが、多分WSFを使う場合に効果を発揮するのだろう。

不要ではあるが、CStr()で明示的に文字列型(のバリアント型)に変換している。

VBScriptにも文字列連結の演算子が存在する。`&'がそれだ。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private Function FizzBuzz(ByVal n)
	Dim str

	str = ""
	If n Mod 3 = 0 Then str = "Fizz"
	If n Mod 5 = 0 Then str = str & "Buzz"
	If str = ""    Then str = n

	FizzBuzz = str
End Function

Private i
For i = 1 To 100
	Print FizzBuzz(i)
Next

文字列連結の演算子は言語によって使用する記号が異なるので、少し興味深い。

PerlRubyPythonなどに比べるとVBScriptの機能は少々プリミティブな傾向にあると思う。特に目立つのが配列だ。Push/Popといった配列サイズを気にせずに操作することができる関数が用意されていないので、C言語malloc/reallocするような感じで常に配列の大きさを気にしておかなくてはならない。更には配列のリテラル表記が存在しない。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private Function FizzBuzz(ByVal n)
	Dim refTbl, fizz, buzz

	refTbl = Array(n, "Fizz", "Buzz", "FizzBuzz")
	fizz = 0
	buzz = 0

	If n Mod 3 = 0 Then fizz = 1
	If n Mod 5 = 0 Then buzz = 2

	FizzBuzz = refTbl(fizz + buzz)
End Function

Private i
For i = 1 To 100
	Print FizzBuzz(i)
Next

配列のリテラル表記がない代わりに組み込み関数のArrayを使うことになる。あるのは嬉しいのだが、便利なのか不便なのかイマイチ分かりにくく感じる。

ハッシュ/連想配列といった機能は組み込まれていないので、COMオブジェクトを使うことになる。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private referenceTable
Set referenceTable = WScript.CreateObject("Scripting.Dictionary")
referenceTable.Add 0, 0
referenceTable.Add 1, "Fizz"
referenceTable.Add 2, "Buzz"
referenceTable.Add 3, "FizzBuzz"

Private Function FizzBuzz(ByVal n)
	Dim fizz, buzz

	referenceTable(0) = n
	fizz = 0
	buzz = 0

	If n Mod 3 = 0 Then fizz = 1
	If n Mod 5 = 0 Then buzz = 2

	FizzBuzz = referenceTable(fizz + buzz)
End Function

Private i
For i = 1 To 100
	Print FizzBuzz(i)
Next

Set referenceTable = Nothing

「Scripting.Dictionary」なのだが、個人的にリテラル表記が用意されていない点が使い難いと感じる。それでも在るだけマシだし、配列と違ってメソッドAddで大きさを気にせずに要素を追加できるので、簡易的に配列の代わりに使うこともできる。

あまり知られていないかもしれないが、VBScriptでもプロシージャ(の参照)をオブジェクトとして扱うことができる。自分で定義した関数に対して組み込み関数GetRefを適用すればよい。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private Function FizzBuzz(ByVal n)
	Dim str

	str = ""
	If n Mod 3 = 0 Then str = "Fizz"
	If n Mod 5 = 0 Then str = str & "Buzz"
	If str = ""    Then str = n

	FizzBuzz = str
End Function

Private Function MakeArray(ByVal size, ByVal initFunc)
	Dim ary()
	ReDim ary(size - 1)

	Dim i
	For i = 0 To UBound(ary)
		ary(i) = initFunc(i)
	Next

	MakeArray = ary
End Function

Private Function FizzBuzzWrapper(ByVal n)
	FizzBuzzWrapper = FizzBuzz(n + 1)
End Function

Private fizBuzAry, val

fizBuzAry = MakeArray(100, GetRef("FizzBuzzWrapper"))
For Each val in fizBuzAry
	Print val
Next

こんな感じだ。難点といえば、VBScriptには無名関数の類が用意されていないので、必ずプロシージャを定義しておく必要がある、という所だろう。もっともCプログラマ視点では「関数ポインタっぽいよね」ということで苦にはならないのだが、他の言語を知っている身としては少々不恰好だという印象を拭いきれない。

ところで、割と知られた話だと思うが、.NET Frameworkがインストールされている場合、オブジェクトによってはVBScriptで使用できたりする。というのも汎用的に使えそうなクラスがCOMオブジェクトとして登録されているのだ。

Option Explicit

Private Sub Print(ByVal str)
	WScript.Echo str
End Sub

Private Function FizzBuzz(ByVal n)
	Dim str

	str = ""
	If n Mod 3 = 0 Then str = "Fizz"
	If n Mod 5 = 0 Then str = str & "Buzz"
	If str = ""    Then str = n

	FizzBuzz = str
End Function

Private fizBuzAry, i, val

Set fizBuzAry = CreateObject("System.Collections.ArrayList")
For i = 1 To 100
	fizBuzAry.Add FizzBuzz(i)
Next

For Each val in fizBuzAry
	Print val
Next

ネット上のサンプルと同様に、System.Collections.ArrayListを使用してみた。VBScriptの配列は、正直な所JScriptなどに比べると機能が原始的で使い辛いので、これなんかは自分用のツールに使う分には重宝するかもしれない。

Windowsでのスクリプト言語PowerShellに移行していくだろうが、依然としてVBScriptは現役だろう。言語自体も結構便利で使いやすいし、何より現行のWindows上でデフォルトで使用できるメリットは大きい。機能が少々プリミティブではあるが、その辺りはライブラリを拵えるなり.NET Frameworkの機能を借りるなりすれば解決できるはずだ。

しかし、ここまで書いてきてアレだが、やはり私としてはJScriptの方が好きだ。VBScriptは悪くないのだが、どうもしっくりこない。

*1:デフォルトでWSH対応なプログラミング言語繋がり。

*2:ADSIで取得したコレクションをEnumeratorで舐めようとしたら「そいつには未対応だ」と言われて出来なかった。

*3:Visual Basicの機能の幾つかは、VBScriptでは省略されている。

*4:正確にはプロシージャへの参照を、だが。