web-dev-qa-db-ja.com

Scala継続とは何ですか?

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

Stack Overflowに関するこの質問:

残念ながら、これらの参照はどれも継続が何のためにあるのか、シフト/リセット機能が何をするのかを定義しようとしておらず、私はそれをする参照を見つけていません。リンクされた記事の例がどのように機能するのか(または何をするのか)推測することができなかったので、私を支援する1つの方法は、これらのサンプルの1つを1行ずつ調べることです。 3番目の記事のこの単純なものでさえ:

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

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

84
Dave

私の blogresetshiftが何をするかを説明しているので、もう一度読んでみてください。

私がブログでも指摘しているもう1つの優れたソースは、ウィキペディアの 継続渡しスタイル に関するエントリです。これは、サブジェクトで最も明確ですが、Scala構文を使用せず、継続が明示的に渡されます。

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

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

今、あなたはScalaページの簡単な例さえも理解していないので、doは私のブログを読んでいます。それは、これらの基本の説明に関係するonly、結果が8

37

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

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

  1. 実行はshiftブロックの残りをスキップし、ブロックの終わりから再開します
    • cfに渡されるパラメーターは、shiftブロックが実行を継続するときに「評価」するものです。これはcfの呼び出しごとに異なる場合があります
  2. 実行はresetブロックの終わりまで(またはブロックがない場合はresetへの呼び出しまで)まで続きます。
    • resetブロック(またはブロックがない場合はreset()のパラメーター)の結果は、cfが返すものです
  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
31
Alex Neth

Scalaの区切られた継続に関する research paper からの標準的な例を考えると、shiftへの関数入力にfという名前が与えられ、no長い匿名。

_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の呼び出しまでの計算が replaced は、fへの関数(たとえばshift)入力に置き換えられます。

置き換えられた計算は、 shifted (すなわち、移動)関数kになります。関数fは、関数kを入力します。ここで、k contains 置換された計算、k入力_x: Int_、 kの計算は、shift(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の入力パラメータのタイプシグネチャによって指定されていることに注意してください。

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

_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つの例の事前の提示によって多少難読化された一貫性のある共通の抽象化が解明されることを願っています。たとえば、正規の最初の例は、 research paper で、私の名前付きfの代わりに匿名関数として提示されたため、一部の読者にはすぐにはわかりませんでした borrowed 2番目の例のreadと抽象的に類似していました。

したがって、区切られた継続は、「あなたはresetの外側から私に電話をかける」から「私はresetの内側に電話をかける」というコントロールの反転の幻想を作り出します。

fの戻り値の型はそうですが、kの戻り値の型はresetの戻り値の型と同じである必要があります。つまり、fには戻り値を宣言する自由がありますkの型は、fresetと同じ型を返す場合に限ります。 readおよびcaptureについても同じです(以下のENVも参照してください)。


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

区切られた継続で純粋な関数を明示的に達成できます。

_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)型推論は ダイヤモンド多重仮想継承をサポートしない に依存するためです。

9

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

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

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

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

したがって、継続はシステムレベルで使用する必要があります。アプリケーションコードにそれらを振りかけることは、悪夢の確実なレシピであり、gotoを使用した最悪のスパゲッティコードよりもはるかに悪いことです。

免責事項: Scalaの継続について深く理解していません。Schemaの例を見て、継続を知っているから推測しました。

8
starblue

私の観点から、最高の説明はここに与えられました: 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
4
Dmitry Bespalov

Scala継続に関する別の(より最近の2016年5月)の記事は次のとおりです。
" Scalaでのタイムトラベル:CPS in Scala(スカラの継続) " by Shivansh Srivastava(shiv4nsh
- Dimry Bespalovanswer で言及されている Jim McBeath記事 も参照しています。

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

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

さらに説明するために、最も古典的な例の1つを使用できます。

サンドイッチのことを考えて、冷蔵庫の前のキッチンにいるとしましょう。すぐそこに続きを入れて、ポケットに入れます。
次に、七面鳥とパンを冷蔵庫から取り出し、サンドイッチを作ります。サンドイッチは現在カウンターの上にあります。
ポケットで継続を呼び出します。サンドイッチのことを考えて、冷蔵庫の前に再び立ちます。しかし、幸いなことに、カウンターの上にサンドイッチがあり、それを作るために使用されたすべての材料がなくなっています。それで食べます:-)

この説明では、sandwichprogram data(たとえば、ヒープ上のオブジェクト)の一部であり、 「make sandwich”ルーチンに戻り、その人は「make sandwich with current continuation”ルーチン。サンドイッチを作成し、実行が中断したところから続行します。

とはいえ、 2014年4月Scala 2.11.0-RC1

次のモジュールを引き継ぐメンテナーを探しています: scala-swingscala-continuations
2.12は、新しいメンテナーが見つからない場合はそれらを含めません
他のモジュール(scala-xml、scala-parser-combinators)の保守を継続する可能性がありますが、ヘルプは大歓迎です。

1
VonC