手順として実装されていない継続の例は何ですか?


15

SOに関するコールバック継続の区別に関する興味深い議論がこの質問を促しました。定義上、継続とは、計算を完了するために必要なロジックの抽象的な表現です。ほとんどの言語では、これは継続的な処理が必要な値を渡す1つの引数プロシージャとして現れます。

純粋に関数型の言語(すべての関数が純粋でファーストクラスの市民である場合)では、継続は関数として完全にモデル化できると思います。これは、結局のところ、これまでの継続を私が以前に理解していた方法です。ただし、世界は状態(ため息)でいっぱいです。したがって、一般的な定義では、継続がプログラムの状態をキャプチャする必要はありません。

私の理解を助けるために、継続が関数よりも抽象的な方法で表現されている関数型言語で例を提供できますか?Schemeを使用すると、現在の継続をファーストクラスの方法(call / cc)で取得できることはわかっていますが、それでも、call / ccに渡される1つの引数プロシージャには、現在の継続が別の形式で与えられているようですcall / cc'd関数が結果を適用できる引数手続き。


おそらく、次のような継続と機能停止の交差点:機能停止によって継続をデータ構造に変換できます。興味深い分野かもしれません。
ダンD.

@DanD。私が読むことができる興味深い文献について何か提案はありますか?そのトピックは便利に聞こえます。
デビッドカウデン

回答:


11

tl; dr; タイプ継続を超える包括的抽象化です


継続は、その入力と出力のタイプです

非手続きベースの継続に最も近いのは、Haskellの継続モナドです。です。これは型として表現され、割り込み、再開、バックトラックなどの型と対話するために多くの関数を使用できます。

そのようなクロージャーを次のようなタイプにカプセル化できます。 Contますとして継続を見たときあなたが得るあなたが「より高いレベルの抽象化」とモナドの抽象化を取得Haskellでは種類、および抽象化の他の形態は、継続の上にあるタイプの代わりに、たとえば、単にプロシージャ

  • タイプがモノイドになるように法則に従う場合、2つの継続を取り、それらの間で代替を行うことができます
  • ファンクターの法則に従う型でクロージャーをカプセル化する場合、型を抽象化して継続の入力または出力型を変更できます
  • の法則に従う型でクロージャーをカプセル化する場合、入力検証や入力変換などの機能を使用して、任意に部分的に継続を適用または装飾できます。 ファンクターの

閉鎖と手順

結局のところ、あなたは基本的に正しいです。継続は「手順」ですが、私はむしろそれをクロージャと呼びます。多くの場合、継続は、バインドされた環境を囲むファーストクラスのクロージャーとして最も適切に表現されます。純粋な関数型言語では、参照がないため、これは特に合理的ではないと言うかもしれません。これは事実ですが、値を囲むことができ、単一の割り当てにより、値と参照を囲むことはまったく同じになります。これにより、Haskellが発生します。

(\x -> \y -> insideYIcanAccess x (and y))

バインディング環境を囲む能力を欠く言語は、技術的にはファーストクラスのクロージャーを欠くかもしれませんが、それでもあります が利用できるいくつかの環境(一般的にはグローバル)があります。

したがって、継続を次のように記述する方が正確だと思います。 ます。特定の方法で使用されるクロージャ。


結論

「継続はプロシージャ以外の方法で実装可能ですか?」の質問に対して いいえ。ファーストクラス関数がない場合は、実際に継続することはできません(関数ポインタはファーストクラス関数としてカウントされるため、代わりに任意のメモリアクセスで十分です)。

ここで、「プロシージャよりも抽象的な方法で継続を表現する方法はありますか?」それを型として表現すると、はるかに大きな抽象化が得られ、継続を非常に一般的な方法で扱うことができ、継続と実行するだけでなく、より多くの方法で継続と対話できます。


1
これは、「継続は、プログラムの残りの結果を取得できるものであれば何でもかまいません」に一般化されます。これは通常、いくつかのコード(プログラムの残りの部分)を必要とするため、ほとんどの言語は関数を使用しています。理論的には、何からでも継続を構築できます。コンパイラでの継続的な変換中に、部分的に満たされたツリーを使用しました。
ダニエル・グラッツァー

1
@jozefgの部分的に満たされたツリーは、式としての計算の適切な表現ですが、結局のところ、実際に記述されるコードは、私が理解しているように、プロシージャと明確に異なることのできない種類の式です。それはさておき、部分的に満たされたツリーはコンパイラ表現です。開発者の表現は、言語の構文とセマンティクスを備えた規範的な計算式であり、開発者の99%に「手順」、「関数」、またはその他のように見えます。あなたのコンパイラ開発者の視点からあわやだ思考
ジミー・ホッファ

2

あなたが好むかもしれない1つの例はコルーチンです。たとえば、LuaのコルーチンまたはPythonまたはC#のイテレーター/ジェネレーターは、単発の継続(一度しか呼び出すことができない継続)とパワーが似ていますが、継続は明示的に関数になりません。代わりに、次の「yield」ステートメントまでコルーチンを進める方法があります。

たとえば、次のPythonプログラムを考えます。

def my_iterator():
   yield 1
   yield 2
   yield 3

def main():
   it = my_iterator()
   x = it.next()
   y = it.next()
   z = it.next()
   print x + y + z

その種類は、明示的なコールバックを備えた次のJavascriptプログラムに似ています。

function my_iterator()
  return function(cb1){
    cb1(1, function(cb2){
      cb2(2, function(cb3){
        cb3(3, function(cb4){
          throw "stop iteration";
        });
      });
    });
  });
}

function main(){
   var getNext1 = my_iterator();
   getNext1(function(x, getNext2){
      getNext2(function(y, getNext3){
         getNext3(function(z, getNext4){
            console.log(x + y + z);
         });
      });
   });
}

Javascriptの例は、各ステップが生成された値を返すことに加えて次の継続を返す必要があるため、ノイズが多くなります(Pythonではite内で継続を追跡します)

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