サーチ…


前書き

継続渡しスタイルは、制御の流れの一形態であり、残りの計算を「継続」引数として関数に渡すことを含みます。問題の機能は、後でその継続を呼び出してプログラムの実行を継続する。継続を考える方法の1つは閉鎖です。 Scala継続ライブラリは、言語にshift / resetれたプリミティブの形で区切られた継続をもたらします。

継続ライブラリ: https : //github.com/scala/scala-continuations

構文

  • reset {...} //続きは、囲むリセットブロックの終わりまで続きます
  • shift {...} //呼び出しの後に続いて、クロージャに渡す連続を作成します
  • A @cpsParam [B、C] // Cの値を作るために関数A => Bを必要とする計算
  • @cps [A] // @cpsParamのエイリアス[A、A]
  • @suspendable // @cpsParamのエイリアス[Unit、Unit]

備考

shift Int.+ resetはプリミティブコントロールフロー構造で、 Int.+はプリミティブ操作であり、 Longはプリミティブ型です。それらは区切られた継続が実際にほとんどすべての制御フロー構造を構築するために実際に使用できるという点でどちらかよりも原始的です。彼らは非常に便利な "アウトオブザボックス"ではありませんが、ライブラリで豊富なAPIを作成する際に真に輝きます。

継続とモナドも密接に関連しています。継続はにすることができる継続モナド 、およびそのため、モナドは継続しているflatMap操作がパラメータとして継続を取ります。

コールバックはコンティニュエーションです

// Takes a callback and executes it with the read value
def readFile(path: String)(callback: Try[String] => Unit): Unit = ???

readFile(path) { _.flatMap { file1 =>
  readFile(path2) { _.foreach { file2 =>
    processFiles(file1, file2)
  }}
}}

readFileへの関数の引数は、 readFileがそれを呼び出してジョブの実行後にプログラムの実行を続けるというreadFileで、継続です。

簡単にコールバック地獄になることを抑止するために、我々は継続ライブラリを使用します。

reset { // Reset is a delimiter for continuations.
  for { // Since the callback hell is relegated to continuation library machinery.
        // a for-comprehension can be used
    file1 <- shift(readFile(path1)) // shift has type (((A => B) => C) => A)
    // We use it as (((Try[String] => Unit) => Unit) => Try[String])
    // It takes all the code that occurs after it is called, up to the end of reset, and
    // makes it into a closure of type (A => B).
    // The reason this works is that shift is actually faking its return type.
    // It only pretends to return A.
    // It actually passes that closure into its function parameter (readFile(path1) here),
    // And that function calls what it thinks is a normal callback with an A.
    // And through compiler magic shift "injects" that A into its own callsite.
    // So if readFile calls its callback with parameter Success("OK"),
    // the shift is replaced with that value and the code is executed until the end of reset,
    // and the return value of that is what the callback in readFile returns.
    // If readFile called its callback twice, then the shift would run this code twice too.
    // Since readFile returns Unit though, the type of the entire reset expression is Unit
    //
    // Think of shift as shifting all the code after it into a closure,
    // and reset as resetting all those shifts and ending the closures.
    file2 <- shift(readFile(path2))
  } processFiles(file1, file2)
}

// After compilation, shift and reset are transformed back into closures
// The for comprehension first desugars to:
reset {
  shift(readFile(path1)).flatMap { file1 => shift(readFile(path2)).foreach { file2 => processFiles(file1, file2) } }
}
// And then the callbacks are restored via CPS transformation
readFile(path1) { _.flatMap { file1 => // We see how shift moves the code after it into a closure
  readFile(path2) { _.foreach { file2 =>
    processFiles(file1, file2)
  }}
}}  // And we see how reset closes all those closures
// And it looks just like the old version!

継続する関数の作成

shiftが区切りのresetブロックの外で呼び出された場合、それを使ってresetブロック内に継続を作成する関数を作成することができます。 shiftのタイプは単に(((A => B) => C) => A)ではなく、実際には(((A => B) => C) => (A @cpsParam[B, C])) 。その注釈は、CPS変換が必要な箇所を示します。 resetなしでshiftを呼び出す関数は、戻り値の型がその注釈に「感染」しています。

resetブロックの内部では、 A @cpsParam[B, C]の値はA値を持つように見えますが、それはちょうどふりをしています。計算を完了するために必要とされる継続が型を持つA => Bので、このタイプは戻らなければならない返すメソッド次のコードBCは「本当の」戻り値型であり、CPS変換後の関数呼び出しの型はCです。

さて、この例は、図書館のScaladocから取ったものです

val sessions = new HashMap[UUID, Int=>Unit]
def ask(prompt: String): Int @suspendable = // alias for @cpsParam[Unit, Unit]. @cps[Unit] is also an alias. (@cps[A] = @cpsParam[A,A])
  shift {
    k: (Int => Unit) => {
      println(prompt)
      val id = uuidGen
      sessions += id -> k
    }
  }

def go(): Unit = reset {
  println("Welcome!")
  val first = ask("Please give me a number") // Uses CPS just like shift
  val second = ask("Please enter another number")
  printf("The sum of your numbers is: %d\n", first + second)
}

ここでaskはマップに継続を格納し、後で他のコードはその「セッション」を取得してクエリの結果をユーザーに渡すことができます。このようにして、 goは実際に非同期ライブラリを使用することができますが、そのコードは通常の命令コードのように見えます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow