福澤テクノロジー

おっさんプログラマーが、主に技術的な話をするブログ。

gotoとクロージャ

goto文の要請

数年前の話。
スクリプト言語Hayatを使ったノベル系のゲームを開発していた。
シナリオ担当者から、「goto文を追加してよ」というリクエストが来た。
えー、goto文なんてプログラムの構造を複雑にしてバグの温床になる機能じゃん。
BASIC言語でよく見られるgotoが複雑に入り組んだプログラムは、実行の順番がよくわからなくなり、プログラムを読んでも構造が把握しきれず、スパゲッティプログラムと呼ばれた。
gotoを使わなくても、ループと条件分岐さえあればプログラムは作れる! として「構造化プログラミング」の流れが起きてプログラミング言語からgotoが追放されていったのは、もう数十年前の話しだ。
そんな機能はつけたくないなあ、と思っていたのだが、


goto文を入れろと言ったら
入・れ・ろ・ッ!

と怒鳴られた。
というのはウソだが、goto文が必要だというのは本当だった。

このゲームではイベントシーンで選択肢が山ほど現れ、分岐しまくったり合流したり前に戻ったり、かなりカオスな状況になるのだった。
フローチャートで書くと、線があちこちにネットワーク状に入り乱れていて、複雑怪奇なものになっていた。
これをループと条件分岐のみで制御するのはかなり難しい。何とか作れることは作れるが、シナリオに変更があればまた頭を悩ませてスクリプトをカット&ペーストして、新たな構造を作る必要がある。
しかもシナリオ変更など日常茶飯事だ。むしろプロジェクトの終わり間際まで最も変更の多くかかる部分だ。
これはgotoで制御する方がわかりやすいし、変更にもむしろ強い。

という事でHayat言語にgoto文を追加する事にした。

クロージャ

Hayatは、ゲーム機でRubyのようなスクリプトを使いたいという動機もあって設計していたので、Rubyのブロックのように、メソッドにクロージャを渡す機能を持っていた。

def method_a (p)
{
  p.call()    # クロージャの呼び出し
}

method_a {
  # クロージャ本体
}

クロージャは単に中括弧 { } で括るだけで書けるので、これがクロージャだと意識する必要もほとんど無い。
こういう言語で goto を入れるにはどうするか。

label :label_x

method_a {
   ...
   method_b {
      ...
      goto :label_x
   }
}

このgotoを期待通りに動かしたい。
同じスコープ内にあるのなら話は簡単だ。単純にジャンプ命令にコンパイルすればいい。
しかし、goto文は2つのメソッドと2つのクロージャの内側にある。ジャンプ先のラベルはその外側にあるのだ。

例外処理との組み合わせ

これを解決するため、gotoは同じスコープ内にラベルが無い場合は、「goto例外」を発行する事にした。
例外はキャッチされなければ、コールスタックを遡ってルーチンの呼び出し元に戻る。
そこでまたジャンプ先ラベルをサーチして、無ければさらにコールスタックを遡る。
コールスタックを深い所に入っていく方向のgotoは実行できない事にした。さすがにこれはどう考えても実装できない。
これで解決した。

この方法を取ることで、面白い事が出来た。

def onGoto (n, *labels) {
  goto labels[n]
}

onGoto(x, :label1, :label2, :label3)

x の値が 0なら :label1 へ、1なら :label2 へ、2なら :label3 へジャンプする。
goto文自体はonGotoメソッドの中にあるのに、これでジャンプが出来るのだ。
あれ、あまり面白くないですか?

必要性

この機能は必要だろうか?
正直、あまり必要ない気がする。
あんな複雑怪奇なフローチャートが必要なゲームなど、めったに出るものではない。
ただ、gotoというのは直感的にかなりわかりやすい。初心者には特に。
ゲームスクリプトを書く人は、プログラマではなく企画や演出の人だったりする。
そういう人達にとって、わかりやすさというのは大きなメリットとなりそうだ。

一方で、これを実装するのに例外処理機構に手を入れる事になった。
僕の設計がまずかったせいもありそうだが、例外処理が重くなった。
例外処理用のデータに、goto用のラベルリストが入り込む事になってしまった。
それと同時にルーチンからのリターン処理にも負荷がかかった。例外がコールスタックを遡る所で共通の処理が走るからだ。
Hayat言語の遅い原因の1つになっている気がする。プロファイリングしてないのでよくわからないけど。

まあ、他の言語で goto を入れようという事になった時には参考にして下さい。
ありえなさそうだけど。