OldGood COMPUTER!! のコーナーを10年ぶりに更新した、と書いたのが数日前。
その後、新資料が見つかってさらに追記している。
特に、Whirlwind の命令コード体系や、アセンブラの記述方法などは興味深い。
8bit 時代の PC でマシン語プログラムやっていた人にはわくわくできる内容だと思うし、最近の高級言語しか知らない世代には、こんな時代があったこと自体が衝撃だと思う。
で、急に思いついて検索してみた。
goto 不要論争って、最近のプログラマ諸氏は知っているのか?
goto 不要論について論じたページを見る限り、まだ心あるプログラマの方には、この論争は認識されていたようだ。
最近の言語では goto が排除される傾向にあるので、もう論争自体過去の遺物かと思っていた。
でも、「認識」はしていても、どうも認識がずれている方向にある。
#もちろん、正しく認識できている人もいるのだが、そういう人は稀なので。
goto は「使いすぎるとスパゲティプログラムになる」のではない。
1980 年代の BASIC のような、GOTO と GOSUB だらけのプログラム、というのも間違いだ。
そもそもこの論争がなんで起こったのか理解しないまま、goto 不要論を持ち出してもおかしな議論にしかならない。
というわけで、goto 不要論争がおこった時代の goto はどんなだったか、という例として、Whirlwind のアセンブラプログラムを見てもらうと面白いと思う。
Whirlwind では、サブルーチンを作る方法は準備されているのだが、それはいわゆる「goto」なのだ。
サブルーチンから戻る命令も、もちろん goto 。
それじゃ、全部同じところに戻ってしまうから、サブルーチンとして使えない?
そんなことはない。サブルーチン終了時の戻り先アドレスを、自己書き換えで変更すればよいのだ。
アセンブラだから、if に相当するのは条件分岐。「分岐」つまり goto だ。
かくして、プログラムの流れはあっちこっちに飛び回り、しかもプログラムリストに書いてあるとび先は、本当のとび先ではない。実行中に書き変えられることが「当然」なのだ。
高級言語から goto を排除する、というのが不要論なのに、アセンブラを例にするのは不適切だ、という声が出そうだ。
でも、アセンブラは当時の「当たり前の」プログラムスタイルなのだ。
そして、これを受け継いだ FORTRAN でも、同じスタイルが適用される。
FORTRAN は、アセンブラの「演算部分」を簡易化する言語だった。だから、FORmula TRANslation なのだ。
そして、演算部分以外の、流れ制御はアセンブラと同じ概念だった。
初期の FORTRAN では、条件を調べた後にできることは、goto のみだ。
その一方で、条件を調べないでも、goto と変数の組み合わせでいろいろな場所に分岐できる。
これは、アセンブラの「自己書き換え」テクニックを高級言語に取り入れたものだった。
もちろん、サブルーチンを呼び出すのも goto で、戻るのも goto だ。
サブルーチン呼び出し時に、どこから呼び出されたかを変数に入れておき、その変数を見て goto 先を変えることで「戻る」のだ。
なんて回りくどいやりかた、なんて思ってはいけない。当時は、先に書いたようにアセンブラも含めて、これが標準的な方法だったのだから。
ところで、FORTRAN には制御構造が3つある。
一つは goto で、もう一つは if 。if でできることは goto だけ、というのは先に書いた通り。
そして3つ目は、ループ命令(DO)だ。
ループ命令は、その後の言語のように、ループブロックの「はじめ」と「おわり」を指定したりはしない。
頭の部分で、どこまでがブロックかを、アドレス(行番号)によって指定するのだ。
つまり、ループ命令でもアドレスを意識した goto の呪縛が残っている。
制御構造がアセンブラと変わらない、というのはそういう意味なのだ。
かくして、FORTRAN の記述は goto だらけ、行番号だらけとなる。
これを称してスパゲティプログラムというのであって、C 言語ではどんなに悪意を持ってgoto を駆使したとしても、FORTRAN の「当たり前」ほどの混乱には至らない。
#C 言語で混乱を起こしたいなら、goto なんかより、もっと別の悪意の使い方があるわけだが。
#FORTRAN の名誉のために書いておくが、これは初期の FORTRAN の話。
現代の FORTRAN はもっと洗練されている。
この状況からみると、if の後ろに命令が複数書ける、1980 年代のマイクロソフト BASIC など、「goto が劇的に少ない」言語だ。goto 不要論で引き合いに出すのは間違いだ。
GOSUB を使ったって、ちゃんと RETURN で呼び出し元に帰れるのだ。
自己書き換えを使ったり、変数でとび先が変わる GOTO と違って、プログラムの流れが追いやすい。何の問題もない。
初期の FORTRAN をはじめとする、当時のプログラム言語の状況はあまりにもひどいものだった。
見かねたダイクストラが「goto を使わないで論理を表現する方法があるはずだ」と示したのが不要論の始まりだが、よく知られているようにダイクストラは goto がダメだと言ったのではない。
プログラムを、ブロックの入れ子構造で示す「構造化」を取り入れると、if の後で goto しかできなかったり、変数によって分岐先が変わったりする goto を使わないようにできるよ、と示したに過ぎない。
ちなみに、ダイクストラが示したのはつまり、ALGOL の表記法だ。
ダイクストラは ALGOL の設計者の一人だった。
初期の ALGOL はプログラム言語というより、アルゴリズム記述方法といったほうが正しい。
実際の動作させることには重点を置いていないので、ブロックを明示的に打ち切って処理を早くするようなことは考慮されていない。
だから、ダイクストラは「そういう場合のために、goto は必要なんじゃないかな」とは思っていたようだ。
じゃぁ、使ってもよいのかというと、使おうにも現代の言語には goto がない。
でも、C 言語は 40年も昔の言語なのにまだ広く使われていて、 goto がある。
ここで、「goto を使ってはならないか」で論争がある。
まぁ、この論争はだいたい決着がついていて、C 言語の機能不備により、使ったほうがよい局面がある、となっている。
でも、みんなこれ以上の突っ込んだ議論にはならないのね。
FORTRAN の goto の嵐と、ダイクストラの「構造化」の思想を知っていれば、C 言語には goto 以外にも goto があることに気づくはずなのに。
先の説明で感がよい人は気づいたと思うが、break と continue は goto を特殊用途向けに改良したものだ。
break はブロック終了後の最初の命令に goto する。continue はループブロックのみで使用可能で、ブロック最後のループ条件判断に goto する。
まぁ、これくらいなら「ブロックの入れ子構造」を壊すほどじゃないから、禁止されるような goto ではない、という意見もあるだろう。
むしろ、積極的に「ブロック」を解釈することで、goto のとび先である「ラベル」を排除している。goto を残すにしても、現代的な、うまい解釈だとほめてよいと思う。
#もっとも、CONTINUE という命令は最初の FORTRAN にもあった。
ただし、ループ末尾で「先頭に戻る」ことを明示して可読性を上げることを目的につくられた命令で、この命令には何の意味もない。
(先に書いたが、FORTRAN のループ末尾は行番号で示されるため)
しかし、形を変えたとはいっても、ダイクストラが示す方法論によれば、break も continue も goto だということになる。
ところが、絶対禁止、という強い否定論者であっても、なぜか break や continue には寛容であることが多い。
#break や continue に当たるものを、「飼いならされた goto」と呼ぶ表現があるらしい。
この呼び名を使う人は、この議論の本質を理解していると思う。
C 言語のプログラムだって、break や continue を使わないで、ALGOL 風に記述することは十分可能だ。
強い否定論者を自認している人がいたら、break 、continue なしでプログラムを書いてみてほしい。
皮肉でいっているのではなく、ちゃんと記述できるし、それはそれで美しいプログラムになるはずだ。
#多少冗長になるが、その冗長さによって、プログラムの流れがわかりやすくなる。
この冗長さはプログラムリスト上のもので、ちゃんと適切に記述すれば、処理が遅くなったりはしない。
goto を否定する人に言わせると、goto がダメな理由は、だいたい3点。
・処理の途中から飛び込んだりするため、非常に読みづらい
・goto の次の処理がどこにあるのか、ラベルを探さないといけない
・そもそもラベルをつける、というのが面倒くさい
すべてまとめていうなら、「プログラムの流れを追いにくくなる」ということだ。
流れが理解できず、プログラマが動作を把握できなくなれば、当然バグの原因となる。
だから、goto 文はダメなのだ、という論理だ。
でも、わざわざこの「ダメな」特徴を持たせた命令が C言語にはある。
存在しているだけでなく、使うことは積極的に推奨されている。
C言語の影響を受けた多くの言語でも、この命令は受け継がれている。
それが switch 文だ。
switch は、指定された変数によって、「ラベルを頼りにして」「ブロックの途中に積極的に飛び込み」、break によって「積極的にブロックから飛び出る」ための命令だ。
つまり、FORTRAN の「変数によるジャンプ命令」…さらに遡れば、アセンブラの自己書き換えテクニックによる各所へのジャンプを、形を変えて残したものに過ぎない。
switch による goto では、case がラベルとなる。
ラベルだから、「実行される」命令ではないし、最後に : を付ける文法になっている。
(一般的に、アセンブラのラベルは末尾に : を付ける)
ラベル部分から飛び込んで処理をはじめ、通常はラベル1つ分の処理が終わると、break で飛び出す。
でも、break しないまま複数のラベルにまたがって処理を続けてもかまわないし、むしろこのテクニックは積極的に利用されている。
この場合、goto を忌み嫌う人の主張する「処理の途中から飛び込む、非常に読みにくいプログラム」を作り出しているわけだが、どういうわけが goto 否定論者でも switch を当たり前に使う。
さて、goto が不要なのか必要なのか、ここでは結論を出そうと思っていない。
というか、これは誰かが結論を出すような問題ではないだろう。
でも、不要論を…どちらの立場からでも…論じようという人は、このレベルの知識は持ってほしい、と思っているだけだ。
それでないと、議論が非常に浅はかなものになるから。
僕個人の見解としては、switch は便利に使っている。
やはり処理の流れが混乱しやすいし、break 忘れ、という凡ミスもよくやるけど、処理の流れに飛び込める便利さは捨てられない。
goto だって同じで、使ったほうがよい局面があれば使う。
ただ、そんな局面になったことは、まだない。
2016.8.24追記
ダイクストラの論文の後に、クヌースが「むしろ適切に goto を使ったほうが良い」ことを示した論文を書いているそうです。
詳細を書いた記事が書かれていました。
「有害なgoto」「時期尚早な最適化」、そしてプログラミングにまつわる神話は諸悪の根源である
ダイクストラ自身 goto の必要性を認めていた、というのは知っていたのですが、上に書いた記事の中には、その根拠や、ダイクストラ自身が自分の論文が曲解されていることを憂いていたことなどが書かれています。
僕の記事に興味を持っていただけた方は、是非上の記事も読んでみてください。
同じテーマの日記(最近の一覧)
関連ページ
エドガー・ダイクストラの誕生日(1930)【日記 14/05/11】
「計算機言語」が生まれた日(1956)【日記 14/10/15】
「計算機言語」が生まれた日(1956)【日記 14/10/15】
ジョン・バッカスの命日(2007)【日記 15/03/17】
jsdeferred でアニメーション【日記 15/11/05】
別年同日の日記
申し訳ありませんが、現在意見投稿をできない状態にしています。 【KyonKyon】 少し違うかな。K&Rの「プログラミング言語C」にgoto文は使うべきでないという一文を読んだFORTRNの技術者がC言語に移行するときに使ってはいけないのかと構造化プログラミングを理解せず、妄信した結果でしょう。彼らは理解していないからgoto文をreturn文に置き換えててドヤ顔しているというのが一般的でしょう。 (2015-10-05 16:00:01)【ばしくし】 SeleniumRCのテストケース記述で使うgotoIfは重宝しますね。while…EndWhileのループ抜けるときとか必須ですし、setEvalやgetEvalと組み合わせた柔軟な記述は便利です。user-extensions.js使わない限り、これがないと条件分岐したテストケース書けませんからね。 (2015-05-11 23:24:14) 【あきよし】 まぁ、Switchも「飼いならされた」gotoですから、ひどいことにはなりません。文章の趣旨は、goto不要論を論じる場合はこれくらい知っておいてね、ということだけなので、良し悪しもまた別です。 (2013-07-02 09:15:01) 【山本】 そりゃswitchは近代の言語が禁止してるループからループの真ん中に飛び込みやifからifの真ん中に飛び込みをしませんからねえ(入れ子でない状態に限り)。それでもgoto排斥派が寛容な理由は解りませんが。 (2013-07-01 09:08:48) |