まわる〜まわるよ、時代はまわる〜
歌姫中島みゆきさんのことを書くのではない。プログラミング言語の反復処理、つまりループのお話である。思いつくのがfor文であろう。他にはwhile文がある。プログラミング言語によっては、do〜loopやdo〜while、repeat〜untilなんてものもある。いずれも条件式がつくので、間違えると無限ループになってしまう。恐ろしいことである。
あえて無限ループを利用したことをお話してみよう。現在、個人プロジェクトとして、中野主一氏が執筆された「マイコン宇宙講座」、「マイコンが解く天体の謎」、「マイコン天文学Ⅰ」、「パソコン天文講座 天体の軌道計算」の各書籍に掲載されているプログラムをPython(PHP)で書き直すということをしている。残念ながら掲載されているプログラムは、N-BASIC、F-BASIC、N88-BASIC、MS-FORTRANで書かれており、現在ではプログラムを入力して、実行するという環境がない。そこで考えたのが、他の言語で書き換えてしまえばよいということである。プログラミング言語はどれがよいかと考えたとき、PythonもしくはPHPを選んだ。理由は移植性の良さである。
PHPではgoto文が使えるため、BASICで書かれたコードを移植する場合は、問題なくおこなえる。PHP単体であれば、Pythonと同じように実行できる。しかし、グラフィックで表するプログラムを考えた時、PHPGDがあるが、Webサーバ上でしか利用できない。実行環境を揃えるには、XAMPPやMAMPといったWebサーバーをインストールして構築する必要がある。さらにプログラム全体を考えた時、HTML/CSSでFormを作るという作業が必要になる。まあ、PHPはもともとがWebサーバー上で利用するものであるからしかたがない。
Pythonでは、goto文がなく、ラベルジャンプもできないため、移植する際は、ひと工夫する必要があるものの、プログラミング言語本体をインストールすることで、簡単に実行環境をととのえることができる。グラフィックで表示するプログラムもWindowsはPythonのインストール時にオプションで指定することで使えるようになる(linuxやmacでは別途インストール)ので問題はない。そこで、テキストのみ出力するプログラムはPHP(単体で実行可能)で、グラフィックの場合はPythonということにしたが、現在では全面Pythonに差し替えることにしている。
さて、別の意味で“時代はまわる”お話をしてしまったが、本題に入る。以下のN-BASIC(一部抜粋のみ)で書かれたコードを見ていただきたい。
300 REM **** SHUTU-BOTU NO KINJI **** ※ 行番号300と行番号400の間の処理はない
400 SG=0: IF HA>15 THEN SG=-1
--(略)--
480 GOSUB 7200: REM APPEAR
490 IF HD=1 THEN 530
500 IF ABS(TD-HA)<2E-03 THEN 530
510 IF IA=5 THEN 530
520 HA=TA: IA=IA+1: GOTO 400
530 IF HD=0 THEN HD=1: ID=0: HA=0: GOTO 300
540 IF ABS(TD-HA)<2E-03 THEN 570
550 IF ID=5 THEN 570
560 HA=TD: ID=ID+1: GOTO 400
--(略)--
※「マイコン宇宙講座」第5章 月 P.177 リスト5−8 月齢表示プログラムより抜粋
この部分をみても何をやっているかわからないが、このコードは月の出没時刻を求める部分である。行番号480、サブルーチンAPPEARで計算された値が、月の出時刻か月の入時刻かを変数HDで判断し、時刻にして0.1分より小さい場合は、行番号570へ飛ぶ。0.1より大きい場合でも近似パスの回数が5回に達したら、行番号570へ飛ぶという処理をしている。
この部分をPythonでコード化しようとした時、頭を抱えてしまった。行番号300および行番号400へジャンプしていることから、全体的にループ処理として考えることができる。行番号570へのジャンプはループ処理を抜けるので、これも問題ない。問題は、行番号490から行番号520までをどう処理するかということである。IF文が3行ある。最初に考えたのが、それぞれの条件式を変更してif〜elseを使うことだったが、それ以降の処理が複雑になってしまってうまくいかなかったのである。
そこでコードをN-BASICと同じようにif文3行を書いてあわせることにし、この部分をループ処理することにした。書き直したのが次のコードである。
result = lib.appear(jd, lg, la, ra, dc, ds, rd, sg, hd, lib.K) // GOSUB 7200
flags = False
while True:
if hd == 1:
break
ta = result[0]
if abs(ta - ha) < 2.0e-3:
break
if ia == 5:
break
ha = ta
ia += 1
flags = True
break
if flags:
continue # GOTO 400
td = result[0]
al = result[1]
if hd == 0:
hd = 1
id = 0
ha = 0
continue # GOTO 300
if abs(td - ha) < 2.0e-3:
break
if id == 5:
break
ha = td
id += 1
continue # GOTO 400
コードを見みればわかるとは思うが、while True:としてループ処理にした。そしてループを抜けるため、break文、抜けたあとにジャンプするために、変数flagsを使った。こうすることで、N-BASICと同じ処理を実現することができたわけである。もっとよいやり方があるかもしれないが、それは他の人にまかせよう。
今回は全体としてループが一重だったが、二重ループでも、同じ処理で行くことができる。だが、太陽と惑星の出没時刻計算では、ループが三重になっており、一番内側のループから一番外側へジャンプするという、Pythonでコードを書く場合、鬼畜としかいいようのないのもある。すでにPythonで書いていて計算された値も問題ないのだが、フラグ、フラグで、いまコードを見ても自分でもわからないことになっている。当然、書き直すことにしている。
最後にGOTO文について、個人的なことを述べて終わりにする。GOTO文は嫌われており、処理を複雑にしてしまうと言われている。だが、それは正しくとも正しくないことである。当時のBASICは、今のように豊富な制御文が用意されていない。N-BASICでもIF文とGOTO文しかなかったのである。IF〜THEN〜ELSEすらないのだ。したがって、GOTO文を多用してしまうのもいたしかたないことなのである。問題は、「どうGOTO文を使うか」なのだ。ジャンプ先でさらにGOTO文を使用し、さらにその先で…というのは、最もだらしないことである。こういった書き方ではなにがなんだかわからなくなってしまう。
そこで、ロジック的にGOTO文を使用するようにする。BASICではサブルーチンを多く用いて、サブルーチンの範囲の中でしかGOTO文を使うようにすれば、見通しのよいものになる。このような使い方が正しい使い方なのである。メインプログラムの中であちこちジャンプするのは、プログラム設計上に問題があるし、思いつきでコードを書いているにすぎないのではないか。GOTO文を嫌うのは構わないが、問題があるのはプログラマー自身ではないのかと疑ってしまうわけである。
PHPでも、goto文が用意されている。ジャンプ先は行番号ではなくラベルである。javascriptにもラベルが用意されており、goto文はないもののcontinue <ラベル>やbreak <ラベル>が用意されている。基本的に考え方は同じである。便利だからと、やたらと使うのは問題がある行為であるというこを理解していただきたい。
なお、GOTO文について異論は認める
以上、おしまいである。