普通にプログラマが生活する上で時間を戻すということはあまりない。
しかし場合によってはプログラマは時間を戻すという行為に手を出すことがある。
そのような話をここでは書いていこうと思う。
- バックアップからデータ復旧するときにプログラマはタイムリープを使う
HDDが吹っ飛んだ。大変だ。こんなこともあろうかとバックアップしておいたデータがある。
バックアップからデータを復旧しよう。
- githubからプログラムを巻き戻す。
プログラムを色々修正したが大きな間違いをしてバグを組み込んでしまった。
こんなこともあろうかと github にバックアップがあるので3つ前のバージョンに戻そう。
- トランザクションでロールバックを使う。
DBを操作中にエラーが発生してしまいデータの不整合が起きそうなとき、プログラマはトランザクションを開始し成功したらコミット、失敗したらロールバックをしてなかったことにします。
どうも普通のプログラマにとって時間を戻すのは不吉なことが多いです。
- パーサコンビネータでバックトラックをする。
本物のプログラマは時間を戻すことを前向きに行います。
パーサを作る際にパーサを組み合わせて大きなパーサを作るパーサコンビネータという手法があります。
パーサコンビネータは予想された文法に従った文字列が入力されれば成功し結果を返しますが、失敗した場合は分岐点までバックトラックして次の選択をするような処理を行います。
- Prologでバックトラックをする。
Prologのバックトラックは強力です。汎用的にバックトラックを用いてパーサを普通のプログラムとして書いて失敗したらやり直す機構が言語自体が持っています。
通常のプログラムでは分岐のチェックをすることで論理的な不整合があった場合に巻き戻すというような処理は行話なかったり、パーサコンビネータのように必要な時だけバックトラックの仕組みを作って巻き戻すことをしますが、Prologは常にバックトラックを使います。パターンマッチガードの一線を超えてプログラムを進め破壊的と思えるような変数の変更を行いますが、途中で問題が起きれば何もなかったかのように分岐点に戻って次の分岐の処理を行います。
Prologのプログラムはタイムリープを使いこなすような普通の感覚ではあり得ないことをします。なので使い方が分かっても気持ち悪いふわふわした気持ちになります。しかし使い慣れることで当たり前になるのでその感覚に慣れてネイティブ感が出るまで使ってみると4次元空間を操作する魔術師のような感覚が得られて使いこなすことができるようになると思います。
例えばFizzBuzzのプログラムをPrologで書いてみましょう。
fizzbuzz(I,fissbuzz):- 0 is I mod 3, 0 is I mod 5,!. %世界線1
fizzbuzz(I,fizz):- 0 is I mod 3,!.%世界線2
fizzbuzz(I,buzz):- 0 is I mod 5,!.%世界線3
fizzbuzz(I,I).%世界線4
:- forall(begin(I,1,20),(fizzbuzz(I,R),writeln(R))).
:- halt.
このプログラムは4つの世界線がありすばる君はレベル1から20までで生まれてfizzbuzzの世界に送り込まれます。
レベル1のすばる君は世界線1で3でお主は割り切れるか。割り切れないな。死ね。ということで殺されてしまいます。しかしすばる君は次の世界線2で生き戻ります。3で割り切れないので死ね。世界線3で生き戻りします。5で割り切れるか。割り切れないなら死ね。世界線4で生き返ります。そのまま数値を返せ。レベル1すばる君は1を持ち帰ります。
レベル3のすばる君はfizzを持ち帰ることができるでしょうし、レベル5のすばる君はbuzzを持ち帰る。
レベル15のすばる君は最初の世界線である fizzbuzz(I,fissbuzz):- 0 is I % 3, 0 is I % 5,!
を旅します。Iは3でも割り切れるし5でも割り切れるのでfizzbuzzを持ってすばる君は帰ります。
というようにタイムリープをしまくるイメージでプログラムするのです。