2

My answer for a recent question about GOTOs and tail recursion was phrased in terms of a call stack. I'm worried that it wasn't sufficiently general, so I ask you: how is the notion of a tail call (or equivalent) useful in architectures without a call stack?

In continuation passing, all called functions replace the calling function, and are thus tail calls, so "tail call" doesn't seem to be a useful distinction. In messaging and event based architectures, there doesn't seem to be an equivalent, though please correct me if I'm wrong. The latter two architectures are interesting cases as they are associated with OOP, rather than FP. What about other architectures? Were the old Lisp machines based on call-stacks?

Edit: According to "What the heck is: Continuation Passing Style (CPS)" (and Alex below), the equivalent of a tail call under continuation passing is not "called function replaces calling function" but "calling function passes the continuation it was given, rather than creating a new continuation". This type of tail call is useful, unlike what I asserted.

Also, I'm not interested in systems that use call stacks at a lower level, for the higher level doesn't require a call stack. This restriction doesn't apply to Alex's answer because he's writing about the fact that other invocation architectures (is this the right term?) often have a call stack equivalent, not that they have a call stack somewhere under the hood. In the case of continuation passing, the structure is like an arborescence, but with edges in the opposite direction. Call stack equivalents are highly relevant to my interests.

4

2 に答える 2

4

「コール スタックのないアーキテクチャ」は通常、あるレベルで「シミュレート」します。特定の汎用レジスタによって。

したがって、「テールコール」は依然として重要です。呼び出し元の関数は、呼び出し点の後に実行を再開するために必要な情報を保持する必要がありますか (呼び出された関数が終了したら)、または呼び出し点の後に実行がないことを知っていますか? 、したがって、代わりに呼び出し元の「実行を再開するための情報」を再利用するだけですか?

したがって、たとえば、テールコールの最適化は、目的のために使用されているリンクリストで実行を再開するために必要な継続を追加しないことを意味する場合があります...これは「コールスタックシミュレーション」として見たいです(あるレベルでは、それはISですが)明らかにより柔軟な取り決めです-継続を渡すファンが私の答えのいたるところにジャンプしたくない;-)。

于 2009-09-28T02:37:25.747 に答える
2

この質問が私以外の誰かに興味を持っているという偶然の機会に、私はこれにも答える他の質問に対する拡張された答えを持っています。これが一言で言えば、厳密ではないバージョンです。

計算システムがサブ計算を実行する場合(つまり、最初の計算は2番目の結果に依存するため、計算が開始され、別の計算が実行されている間は一時停止する必要があります)、実行ポイント間の依存関係が自然に発生します。コールスタックベースのアーキテクチャでは、関係はトポロジカルにパスグラフです。CPSでは、これはツリーであり、ルートとノードの間のすべてのパスが継続です。メッセージパッシングとスレッド化では、これはパスグラフのコレクションです。同期イベント処理は、基本的にメッセージパッシングです。サブ計算を開始するには、依存関係を拡張する必要があります。ただし、リーフに追加するのではなく、リーフを置き換える末尾呼び出しを除きます。

末尾呼び出しを非同期イベント処理に変換するのはより複雑なので、代わりに、より一般的なバージョンを検討してください。Aがチャネル1のイベントにサブスクライブされ、Bがチャネル2の同じイベントにサブスクライブされ、Bのハンドラーがチャネル1でイベントを発生させるだけの場合(チャネル間でイベントを変換する)、Aはチャネルのイベントにサブスクライブできます。 Bをサブスクライブする代わりに、2。これは、末尾呼び出しに相当するものが

  • Aがチャネル2でサブスクライブされると、チャネル1でのAのサブスクリプションはキャンセルされます。
  • ハンドラーは自己購読を解除します(呼び出されると、購読をキャンセルします)

ここで、サブ計算を実行しない2つのシステム:ラムダ計算(または一般に項書き換えシステム)とRPNについて説明します。ラムダ計算の場合、末尾呼び出しは、項の長さがO(1)である一連の削減にほぼ対応します(SICPセクション1.2の反復プロセスを参照)。RPNを使用して、データスタックと操作スタック(操作のストリームではなく、操作はまだ処理されていないもの)と、シンボルを一連の操作にマップする環境を使用します。末尾呼び出しは、O(1)スタックが増加するプロセスに対応する可能性があります。

于 2009-10-10T05:06:22.850 に答える