時代遅れひとりFizzBuzz祭り、今回はHSP (Hot Soup Processor)。前回のActiveBasicと同様に、特に使うわけではないのに何故かHDDの中に入っていた言語だ。
ついでにいうと、ActiveBasicもHSPも日本発の比較的マイナー且つ処理系がフリーなプログラミング言語で、Windows用のアプリケーション開発を指向している節がある――という点で共通点があると思う。
ただActiveBasicが「Windows APIを直叩きしてゴリゴリ」という感じであるのに対して、HSPはその辺りの下回りを隠蔽してそこそこ簡単に使えるようになっている。
その反面、ActiveBasicは汎用のプログラミング言語的で構文もそれなりに普通なのだがHSPの構文は(一般的な汎用の言語と比較すると)どこか貧弱だ。HSP 3.xは以前のバージョンよりも構文を含めて拡張されていると思うが、それでも他言語経験者から見ると貧弱に見える。
だからではないが「HSPは特定領域やゲーム向けのDSL」といった類の発言には頷けるものがある。少なくともWindows上でGUIやグラフィック/サウンド機能を手軽に扱うことに長けたツールだが、しかし何でもHSPでやろうとすると落とし穴に嵌ってしまう。
そういいつつも、この「時代遅れひとりFizzBuzz祭り」はコンソールでFizzBuzzしてきたので、今回もコンソール用のプログラムを書く。HSPの長所を全く生かしてない気もするが、気にしない。ちなみにHSP 3.21aだ。
まず処理系付属のドキュメントをぱらぱらと眺めながら*1書いてみた、最初のバージョン。
#runtime "hsp3cl" #packopt name "fizzbuzz" msg = "", "Fizz", "Buzz", "FizzBuzz" for i, 1, 101 msg(0) = str(i) mes msg((i\3 = 0) + ((i\5 = 0) * 2)) next end
配列の定義やアクセスの書き方が何となく慣れない。普段、配列を定義する時や配列の要素にアクセスする時に専用に記法が用意されている言語を使っているからだろう。それとC言語系統の言語では剰余演算子は`%'だが、HSPでは`\'だったりする。もっとも剰余演算のあたりは言語によって結構違うので*2、あまり気にならない。
HSPのドキュメントやサンプルコードでは演算子の前後やコンマの後に空白を入れていないが、このFizzBuzzでは読みやすさ重視で空白を入れるようにしている。
実は当初はifを使った多岐分岐で書こうとしたのだが、HSPのif - elseにはelse ifにあたる構文がないので諦めた。しかし頑張ればそれっぽい感じに記述できるようだ。
#runtime "hsp3cl" #packopt name "fizzbuzz" for i, 1, 101 if i\15 = 0 { mes "FizzBuzz" } else : if i\3 = 0 { mes "Fizz" } else : if i\5 = 0 { mes "Buzz" } else { mes i } next end
もっともこのコード、実はelse節の中にif - elseを入れ子にしているだけである、多分。
forは標準の命令ではなくマクロとして定義されている*3。C言語のfor文ではなくVBScriptやLuaのそれに近いが、終了値の取り扱いが異なる点がやや紛らわしい。例えばVBScriptのForでは終了値までループを実行するが、HSPでは終了値になった時点でループを抜けるのだ。
マクロではないループ用の命令もちゃんと存在する。HSP本来の繰り返し命令はrepeatだ。
#runtime "hsp3cl" #packopt name "fizzbuzz" repeat 100, 1 msg = "" if cnt\3 = 0 : msg = "Fizz" if cnt\5 = 0 : msg += "Buzz" if msg = "" : msg = cnt mes msg loop end
このrepeatも、他言語の経験者としては「ループ回数, ループ変数cntの初期値」の順に指定する点に違和感を覚えるのだが、しかし単純に「指定回数だけ繰り返す」という視点では妥当なのかもしれない。
HSPには処理を分割する為の機能が3つある。サブルーチン、ユーザ定義命令、ユーザ定義関数だ。
サブルーチンを使って処理を分割してみると、例えばこんな感じになる。
#runtime "hsp3cl" #packopt name "fizzbuzz" repeat 100, 1 gosub *fizzbuzz loop end *fizzbuzz msg = "" if cnt\3 = 0 : msg = "Fizz" if cnt\5 = 0 : msg += "Buzz" if msg = "" : msg = cnt mes msg return
gosubで指定したラベルにジャンプして、returnでジャンプ元に戻っているだけだ。アセンブラ*4のサブルーチン的だ。引数や内部変数なんて大層なものは存在しない。
ユーザ定義命令はサブルーチンよりも遥かに近代的で、引数を取れる。パラメータlocalを使えば内部変数も使用できるが、この例では内部変数を使わなくても何とかなるので使用していない。
#runtime "hsp3cl" #packopt name "fizzbuzz" repeat 100, 1 fizzbuzz cnt loop end #deffunc fizzbuzz int n msg = "" if n\3 = 0 : msg = "Fizz" if n\5 = 0 : msg += "Buzz" if msg = "" : msg = n mes msg return
localを使わないと変数はグローバルなものになってしまう。モジュール定義命令を併用すれば局所化することは可能である。とはいえ、他言語経験者的には違和感があるというか、誰かが使用している変数を誤って別のところで弄ってしまいそうで怖い。
値を返したい場合はユーザ定義関数を定義すればよい。
#runtime "hsp3cl" #packopt name "fizzbuzz" repeat 100, 1 mes fizzbuzz(cnt) loop end #defcfunc fizzbuzz int n msg = "" if n\3 = 0 : msg = "Fizz" if n\5 = 0 : msg += "Buzz" if msg = "" : msg = n return msg
しかし#deffuncと#defcfuncという名前も微妙だ。サブルーチンという言葉は既に似たようなgosub命令があるので避けるとして、例えば#deffuncを#defprocに、#defcfuncを#deffuncにすると他言語経験者でも違和感を感じないかもしれない*5。
触ってみて思ったのだけど、HSPを巡っては、
……みたいな構図があるように思う。例えばrepeatはプログラマ視点では違和感がある命令なのだが、しかし「単純に指定した回数繰り返す」という素人でも発想できる内容にはマッチしている*6。
まあ、何というか、結局プログラマと非プログラマで発想が違ってくるという、何処の分野でも通用する当たり前の話なのだろう*7。
個人的には、Windows上でグラフィックとかサウンドとか手っ取り早く使うためのDSL的なポジションの言語である。
*1:実際にはHTMLのドキュメントなので、「ぱらぱらと」なんてあり得ないが……要するに適当に眺めつつ、気になる所だけピンポイントに読んでいただけ。
*2:例えばFortranでは関数mod、VBScriptでは演算子Mod、REXXでは演算子`//'。
*3:プログラム制御マクロというらしい。
*4:といっても私は偽アセンブラのCASL IIしか知らないのだが……。
*5:Pascalの流儀。値を返す関数(function)と、値を返さない手続き(procedure)に分ける。
*6:もっともREXXのdoループみたいに素人発想にもプログラマ視点にも対応できる文法にする、という選択肢もあったと思うのだが……。
*7:その道の人と外野では考え方が違う、というごくごく普通の話。