86

Programming in Scalaを終えたばかりで、Scala 2.7 と 2.8 の間の変更点を調べています。最も重要と思われるのは継続プラグインですが、それが何に役立つのか、どのように機能するのかわかりません。非同期 I/O に適していることがわかりましたが、その理由はわかりませんでした。このテーマに関するより人気のあるリソースのいくつかは次のとおりです。

そして、スタックオーバーフローに関するこの質問:

残念ながら、これらの参考文献はいずれも、継続の目的やシフト/リセット関数の役割を定義しようとはしていません。リンクされた記事の例がどのように機能するか (またはそれらが何をするか) を推測することはできませんでした。3番目の記事のこの単純なものでさえ:

reset {
    ...
    shift { k: (Int=>Int) =>  // The continuation k will be the '_ + 1' below.
        k(7)
    } + 1
}
// Result: 8

なぜ結果は 8 なのですか? それはおそらく私が始めるのに役立つでしょう。

4

7 に答える 7

38

私のブログでは、何をどのようresetshift行うかについて説明しているので、もう一度読みたいと思うかもしれません。

私のブログでも紹介しているもう 1 つの良い情報源は、ウィキペディアの継続渡しスタイルに関するエントリです。これは、Scala 構文を使用しておらず、継続が明示的に渡されていますが、この件に関しては群を抜いて最も明確です。

私のブログでリンクしているが壊れているように見える区切り継続に関する論文は、多くの使用例を示しています。

しかし、区切られた継続の概念の最も良い例は Scala Swarm だと思います。その中で、ライブラリはコードの実行をある時点で停止し、残りの計算は継続になります。次に、ライブラリは何かを行います。この場合、計算を別のホストに転送し、結果 (アクセスされた変数の値) を停止された計算に返します。

さて、あなたは Scala ページの簡単な例さえ理解していないので私のブログを読んでください。その中で、私はこれらの基本を説明することにのみ関心があります.なぜ結果が8.

于 2009-10-03T15:04:35.233 に答える
31

既存の説明は、私が望むほど概念を説明するのに効果的ではないことがわかりました. これが明確で正しいことを願っています。私はまだ継続を使用していません。

継続関数cfが呼び出されると:

  1. 実行はブロックの残りをスキップし、shiftその最後で再び開始します
    • 渡されたパラメーターは、実行が継続するときにブロックが「評価」するものですcfshiftこれは、への呼び出しごとに異なる場合がありますcf
  2. ブロックの最後まで (または、ブロックがない場合は resetへの呼び出しまで)実行が継続されます。reset
    • ブロックの結果(または、ブロックがない場合は ()resetへのパラメーター) が返されます。resetcf
  3. ブロックcfの最後まで実行が続くshift
  4. 実行はブロックの最後までスキップしresetます (またはリセットの呼び出し?)

この例では、A から Z までの文字に従ってください。

reset {
  // A
  shift { cf: (Int=>Int) =>
    // B
    val eleven = cf(10)
    // E
    println(eleven)
    val oneHundredOne = cf(100)
    // H
    println(oneHundredOne)
    oneHundredOne
  }
  // C execution continues here with the 10 as the context
  // F execution continues here with 100
  + 1
  // D 10.+(1) has been executed - 11 is returned from cf which gets assigned to eleven
  // G 100.+(1) has been executed and 101 is returned and assigned to oneHundredOne
}
// I

これは以下を出力します:

11
101
于 2009-11-05T12:02:23.980 に答える
9

Scalaの区切られた継続に関する研究論文の標準的な例を考えると、への関数入力にshift名前が付けられ、f匿名ではなくなるように少し変更されています。

def f(k: Int => Int): Int = k(k(k(7)))
reset(
  shift(f) + 1   // replace from here down with `f(k)` and move to `k`
) * 2

Scalaプラグインは、この例を変換して、resetそれぞれshiftからの呼び出し までの(の入力引数内の)計算が、への関数(たとえば)入力にreset置き換えられるようにします。fshift

置き換えられた計算は、関数にシフト(つまり移動)されますk。関数は関数をf入力しますk。ここで、置換された計算、入力、および置換の計算がk 含まれます。kx: Intkshift(f)x

f(k) * 2
def k(x: Int): Int = x + 1

これは次と同じ効果があります:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1

Int入力パラメーターxの型(つまり、の型シグニチャーk)は、の入力パラメーターの型シグニチャーによって指定されていることに注意してくださいf

概念的に同等の抽象化を使用した別の借用例、つまり:readへの関数入力です。shift

def read(callback: Byte => Unit): Unit = myCallback = callback
reset {
  val byte = "byte"

  val byte1 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "1 = " + byte1)
  val byte2 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "2 = " + byte2)
}

これは、次の論理的等価物に変換されると思います。

val byte = "byte"

read(callback)
def callback(x: Byte): Unit {
  val byte1 = x
  println(byte + "1 = " + byte1)
  read(callback2)
  def callback2(x: Byte): Unit {
    val byte2 = x
    println(byte + "2 = " + byte1)
  }
}

これにより、これら2つの例を事前に提示したことで多少わかりにくくなった、一貫性のある共通の抽象化が明らかになることを願っています。たとえば、正規の最初の例は、私の名前ではなく無名関数として研究論文に提示されたため、一部の読者には、借用した2番目の例fのに抽象的に類似していることがすぐにはわかりませんでした。read

resetこのように区切られた継続は、「あなたは私を外側から呼ぶ」から「私はあなたを内側に呼ぶ」への制御の反転の錯覚を生み出しますreset

の戻り型は、の戻り型fk同じである必要がありますが、同じである必要はありません。resetつまり、と同じ型を返す限り、f任意の戻り型を宣言する自由があります。との同上(下記も参照)。kfresetreadcaptureENV


区切られた継続は、状態の制御を暗黙的に反転させるものではありません。たとえばreadcallback純粋関数ではありません。したがって、呼び出し元は参照透過性の式を作成できず、したがって、意図された命令型セマンティクスに対する宣言型(別名透過性)の制御がありません。

継続が区切られた純粋関数を明示的に実現できます。

def aread(env: ENV): Tuple2[Byte,ENV] {
  def read(callback: Tuple2[Byte,ENV] => ENV): ENV = env.myCallback(callback)
  shift(read)
}
def pure(val env: ENV): ENV {
  reset {
    val (byte1, env) = aread(env)
    val env = env.println("byte1 = " + byte1)
    val (byte2, env) = aread(env)
    val env = env.println("byte2 = " + byte2)
  }
}

これは、次の論理的等価物に変換されると思います。

def read(callback: Tuple2[Byte,ENV] => ENV, env: ENV): ENV =
  env.myCallback(callback)
def pure(val env: ENV): ENV {
  read(callback,env)
  def callback(x: Tuple2[Byte,ENV]): ENV {
    val (byte1, env) = x
    val env = env.println("byte1 = " + byte1)
    read(callback2,env)
    def callback2(x: Tuple2[Byte,ENV]): ENV {
      val (byte2, env) = x
      val env = env.println("byte2 = " + byte2)
    }
  }
}

明示的な環境のため、これは騒がしくなりつつあります。

正直なところ、ScalaにはHaskellのグローバル型推論がないunitため、Haskellのグローバル(Hindley-Milner)型推論のため、状態モナドへの暗黙的なリフティングをサポートできませんでした(明示的な環境を隠すための1つの可能な戦略として)。ダイヤモンドの複数の仮想継承をサポートしていないことに依存します。

于 2012-11-05T16:21:36.467 に答える
8

継続は、後で呼び出される計算の状態をキャプチャします。

シフト式を残すこととリセット式を関数として残すことの間の計算を考えてみてください。シフト式内では、この関数は k と呼ばれ、継続です。何度でも受け渡したり、後で呼び出すことができます。

リセット式で返される値は、=>の後のシフト式内の式の値だと思いますが、これについてはよくわかりません。

したがって、継続を使用すると、かなり任意でローカルではないコードを関数にまとめることができます。これは、コルーチンやバックトラッキングなどの非標準の制御フローを実装するために使用できます。

したがって、継続はシステム レベルで使用する必要があります。goto を使用した最悪のスパゲッティ コードよりもはるかに悪いことです。

免責事項:私は Scala の継続について深く理解していません。例を見て、Scheme の継続を知っていることから推測しただけです。

于 2009-10-03T06:57:46.437 に答える
5

私の観点から、最良の説明はここで与えられました: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html

例の 1 つ:

制御フローをもう少し明確に見るために、次のコード スニペットを実行できます。

reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}

上記のコードが生成する出力は次のとおりです。

A
B
D
E
G
F
C
于 2016-04-19T16:59:33.407 に答える
1

Scala の継続に関する別の (より最近の - 2016 年 5 月) 記事は、
Scala でのタイム トラベル: Scala での CPS (scala の継続)」by Shivansh Srivastava ( shiv4nsh)です。
また、 Dmitry Bespalov回答で言及されているJim McBeath記事も参照しています。

しかしその前に、継続を次のように説明します。

継続は、コンピュータ プログラムの制御状態の抽象的な表現です
したがって、実際に意味するのは、プロセス実行の特定の時点での計算プロセスを表すデータ構造であるということです。作成されたデータ構造は、ランタイム環境に隠されるのではなく、プログラミング言語からアクセスできます。

それをさらに説明するために、最も古典的な例の 1 つを挙げることができます。

たとえば、キッチンで冷蔵庫の前にいて、サンドイッチのことを考えているとします。あなたはその場で続きを取り、ポケットに入れます。
次に、冷蔵庫から七面鳥とパンを取り出して、自分でサンドイッチを作り、それが現在カウンターに置かれています。
ポケットの中で続きを呼び出すと、サンドイッチのことを考えながら、再び冷蔵庫の前に立っていることに気づきます。でも幸いなことに、カウンターにはサンドイッチがあり、それを作るための材料はすべてなくなっています。だからあなたはそれを食べます。:-)

この説明では、プログラム データsandwichの一部(たとえば、ヒープ上のオブジェクト) であり、"<code>make サンドイッチ" ルーチンを呼び出してから戻るのではなく、"<code>make サンドイッチと現在のこのルーチンは、サンドイッチを作成し、実行が中断したところから続行します。

そうは言っても、2014 年 4 月に Scala 2.11.0-RC1 について発表されたように

次のモジュールを引き継ぐメンテナーを探しています: scala-swingscala-continuations
新しいメンテナが見つからない場合、2.12 には含まれません
他のモジュール (scala-xml、scala-parser-combinators) のメンテナンスは継続する可能性がありますが、引き続きサポートをよろしくお願いします。

于 2016-08-01T18:20:35.670 に答える