whileループの理論と実装をつなぐ方法は?


8

私は教育目的で自分の小さなプログラミング言語に取り組んでおり、少し問題に遭遇しました。それにはいくつかの異なる解決策がありますが、それらはすべて洗練されていないように見えます-私が理解している限りでは不要です。しかし、私が持っている本やグーグル検索を読んでも、エレガントな解決策を見つけることができません。

だから問題は、私が理解しているように、基本的なラムダ計算を構築していることです。true / falseを抽象用語として定義しました。これらを関数と組み合わせて、if / then / elseの動作を実行できます。問題はループで発生します。再帰によって基本的なwhileループを定義できますが、実際にはスタックオーバーフローが発生します。私が理解しているように、通常の解決策はテールコールの最適化を実行することですが、どうすればよいかわかりません。条件文は言語で定義されています。そのため、コンパイラーは、whileループの本体が末尾にあること認識しません。

ドラゴンブックは、ラベルとゴトがあると仮定してループを実装することに焦点を当てています。私は確かにそれをすることができました。ループ構造を組み込まない他の言語は、少なくとも条件文を組み込んでからTCOを行うように見えます。そして、私も確かにそれを行うことができました。しかし、私の理解では、抽象化を適用して削減を実行できる限り、ループ(およびその他すべて)はこれらの基本ブロックから構築できるはずです。

それで私は何が欠けていますか?それとも、「XとYがあれば何でもモデル化できる」が「実際のコンピュータでXとYがあれば何でもモデル化できる」とは異なり、実用的に組み込みが必要なケースの1つですか?目的?


最後の段落であなた自身の質問に答えたと思います。理論はあなたが何かを行うことができると言っているからといって、それを行うことが実用的であるとは限りません。
2015

1
多くの言語には条件と再帰があり、末尾呼び出しの最適化を実装しています。ドラゴンブックを超えて検索します。
デイブクラーク

λλ

svick-確かに、しかし、学習者として、それがここに当てはまるのか、私が何かを知らないのかはわかりません。dave clarke-多くの言語が条件文を組み込み、末尾呼び出しの最適化を実装しています。私は検索を行ったが、言語内の条件付きおよびTCOの結果を生成しなかった。見落としている参照がある場合... Andrej Bauer-かなりではありませんが、十分に近いです。組み込み型、組み込み関数はありません。関数を宣言し、関数を適用できます。私の特定の状況について深く掘り下げると、お粗末な質問になります。
Telastyn 2015

1
@Raphaelラムダ計算を中間言語として使用することは、1970年代から1980年代には大きな問題でした。私の意図はセマンティックな最適化を検出することだったと思います。私の理解(私はコンパイルテクニックの専門家ではないことに注意してください)は、セマンティックな最適化は本当に難しいことがわかりましたが、ローカルの最適化は多額の費用がかかる可能性があり、レジスタ割り当てとgotoの適度な使用により、言語で見やすくなります。それでも、ラムダ計算のアイデアは、コンパイラーの設計に関連しています。たとえば、単一代入のアイデアや継続のコンセプトなどです。
Gilles「SO-邪悪なことをやめなさい」

回答:


5

だから私は今日この問題を解決することができました。私のwhileループのコード:

while (condition: ~>bool) (body: ~>void) => void {
    if condition { 
        body; 
        while condition body; 
    };
}

これをCILにビルドしようとすると(スタックベースのランタイム、疑似コードには重要で、答えには重要ではありません)、次のようになります。

ldarg 0
<build closure from { body; while condition body; }>
call if

私が欠けていた重要なことは、whileコードでは条件付きのものが末尾の位置にあるということです。コンパイラーの観点から見ると、ブロックとwhile関数は2つの別個の関数であり、2つの別個の「テール」があります。これらはそれぞれ、テール位置を簡単に評価できるため、組み込みの条件がなくても最適化を実行できます。


5

あなたはの概念を見逃していると思います 継続。コンパイラーはその概念に依存しないかもしれませんが、ソース言語または中間(またはターゲット)言語として関数型言語を使用するコンパイラーデザイナーとして、その概念を理解し、これを覚えておくことが重要です。

コードの一部は、コードの目的を説明します。命令的に言えば、コードがジャンプまたはフォールスルーする場所だけでなく、その時点でのプログラムの状態(スタックおよびヒープ)も具体化します。ラムダ計算の用語では、サブタームの継続は、サブタームが評価されるコンテキストです。

命令型コードを関数型言語に変換するときの難しさの1つは、いくつかの異なる方法で終了する可能性のあるコードに対処することです。たとえば、コードは例外を返すか、発生させることができます。または、ループの本体で条件の再チェックに進むか、ループを完全に終了することができます(break構成)。これに対処するには、主に2つの方法があります。

  • 多重化:可能なすべての出口に対してコードが合計タイプを返すようにします。ループ本体の場合は、になりますContinue | Break
  • 継続渡しスタイル:コードを、次に実行する関数である追加パラメーターを取る関数に変換します。この追加パラメーターは、関数の続きです。さまざまな方法で終了できるコードは、方法ごとにそのようなパラメーターを1つ受け取ります。

λバツyバツλバツyyバツyは2つの可能な継続であり、ブール値は一方または他方の継続を選択する「if」ステートメントです。

継続渡しスタイルでは、

while (condition) body

に翻訳されます

let rec f (continuation) =
  if (condition, body (f (continuation)), continuation)

継続渡しスタイルの典型的な命令型言語でのプログラムの翻訳では、継続は常にコードの一部が実行される最後のものです。たとえば、body上記の継続はのすべてのコードの後に​​実行されるbodyため、末尾呼び出しの最適化によりbody、継続を実行する直前のすべてのローカル変数が解放されます。

一部の言語では、call-with-current-continuationのような構造を持つファーストクラスの継続が提供されています。Call / ccは通常、末尾呼び出しの最適化に対応できません。プログラム全体の状態の複製につながる可能性があるため、実際にはかなり高価な操作になる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.