私は今、チューリングマシンが計算の一般的なモデルであることを自分自身に納得させる方法について考えています。私は、いくつかの標準教科書、例えばSipserの教会チューリング論文の標準的な扱いがあまり完全ではないことに同意します。チューリングマシンからより認識可能なプログラミング言語に移行する方法のスケッチを次に示します。
if
and while
ステートメント、非再帰定義関数およびサブルーチン、名前付きブールランダム変数および一般ブール式、および増分または減分できるtape[n]
整数配列ポインターを含む単一の無制限ブール配列を持つブロック構造プログラミング言語を検討しn
ます。n++
またはn--
。ポインターn
は最初はゼロで、配列tape
は最初はすべてゼロです。そのため、このコンピューター言語はCライクでもPythonライクでもかまいませんが、そのデータ型は非常に限られています。実際、それらは非常に制限されているためn
、ブール式でポインターを使用する方法すらありません。仮定してtape
が右に無限にある場合、n
負の値になった場合、ポインターアンダーフロー「システムエラー」を宣言できます。また、私たちの言語には、exit
ブール値の答えを出力するために、1つの引数を持つステートメントがあります。
そして、最初のポイントは、このプログラミング言語がチューリングマシンの優れた仕様言語であることです。テープ配列を除いて、コードには、宣言されたすべての変数の状態、現在の実行行、およびそのサブルーチンスタックという有限の状態しかありません。後者は、再帰関数が許可されていないため、有限量の状態しかありません。このタイプのコードから「実際の」チューリングマシンを作成する「コンパイラ」を想像できますが、その詳細は重要ではありません。ポイントは、かなり良い構文を備えたプログラミング言語ですが、非常に原始的なデータ型があることです。
残りの構成は、これをライブラリ関数とプリコンパイル段階の有限リストを備えたより住みやすいプログラミング言語に変換することです。次の手順を実行できます。
プリコンパイラを使用すると、ブールデータ型を、ASCIIなどの大きくても有限のシンボルアルファベットに拡張できます。tape
この大きなアルファベットの値をとると仮定できます。ポインタのアンダーフローを防ぐためにテープの先頭にマーカーを残し、テープの端にTMが誤って無限に滑ってしまうのを防ぐためにテープの端に可動マーカーを残すことができます。シンボル間の任意のバイナリ演算、およびブールfor if
およびwhile
ステートメントへの変換を実装できます。(実際に使用できなかった場合if
でも実装while
できます。)
テープのランダムアクセスと(正の)整数演算の両方を実装するには、無制限の整数データ型が必要です。この目的のために、我々はシミュレートいくつかの固定用-tapeのTM、我々が持っていることを1本のテープでを。この構造は、Sipserの定理として与えられます。アイデアは、エミュレートされたテープを、ヘッドポジションを表すマーカーシンボルで低レベルテープにインターリーブすることです。低レベルテープポインターがゼロの場合、位置移動し、一度にステップジャンプして、番目のサブテープを処理します。読み取りまたは書き込みが行われるたびに、低レベルのテープポインターは0に戻ります。前の段階と同様に、これをプリコンパイラとして実装する方が簡単です。k i i kkk私私k
1つのテープをシンボル値の「メモリ」として指定し、他のテープを符号なしの整数値の「レジスタ」または「変数」として指定します。整数を終端マーカー付きのリトルエンディアンバイナリに格納します。まず、レジスタのコピーとレジスタのバイナリデクリメントを実装します。それとメモリポインタの増分および減分を組み合わせて、シンボルメモリのランダムアクセスシークを実装できます。また、整数のバイナリ加算と乗算を計算する関数を作成できます。ビット演算を使用したバイナリ加算関数と、左シフトを使用して2を乗算する関数を記述するのは難しくありません。(または、リトルエンディアンであるため、本当に右にシフトします。)これらのプリミティブを使用して、長い乗算アルゴリズムを使用して2つのレジスタを乗算する関数を作成できます。
我々は、一次元のシンボル列からメモリテープを再編成することができるsymbol[n]
二次元のシンボル列にsymbol[x,y]
式を用いてn = (x+y)*(x+y) + y
。メモリの各行を使用して、符号なし整数を終了記号付きのバイナリで表現し、1次元のランダムアクセス整数値メモリを取得できますmemory[x]
。メモリから整数レジスタへの読み取りと、レジスタからメモリへの書き込みを実装できます。符号付きおよび浮動小数点演算、シンボル文字列などの多くの機能を関数で実装できるようになりました。
厳密にプリコンパイラ、つまり再帰関数が必要な基本機能はもう1つだけです。これは、インタープリター言語の実装に広く使用されている手法を使用して実行できます。各高レベルの再帰関数に名前文字列を割り当て、低レベルコードを1つの大きなwhile
ループに編成します。このループは、通常のパラメーター(呼び出しポイント、呼び出された関数、引数のリスト)で呼び出しスタックを維持します。
この時点で、構築には高度なプログラミング言語の十分な機能があり、さらなる機能はCS理論というよりもプログラミング言語とコンパイラのトピックです。また、この開発された言語でチューリングマシンシミュレータを作成するのも簡単です。この言語用のセルフコンパイラを書くことは、正確ではありませんが、確かに標準です。もちろん、このようなC言語またはPython言語のコードから外部TMを作成するには、外部コンパイラが必要ですが、これはどのコンピューター言語でも実行できます。
このスケッチされた実装は、論理関数の再帰関数クラスのチャーチチューリング論文だけでなく、決定論的計算に適用される拡張(すなわち、多項式)チャーチチューリング論文もサポートすることに注意してください。つまり、多項式のオーバーヘッドがあります。実際、RAMマシンまたは(私の個人的なお気に入りの)ツリーテープTMが与えられた場合、これはRAMメモリを使用したシリアル計算の多対数オーバーヘッドに減らすことができます。