Suche…


Einführung

Continuation Passing Style ist eine Form des Kontrollflusses, bei der der Rest der Berechnung als "Fortsetzung" -Argument an Funktionen übergeben wird. Die fragliche Funktion ruft diese Fortsetzung später auf, um die Programmausführung fortzusetzen. Eine Möglichkeit, sich eine Fortsetzung vorzustellen, ist die Schließung. Die Scala Fortsetzungen Bibliothek begrenzt Fortsetzungen in Form der Primitiven bringt shift / reset auf die Sprache.

Fortsetzungsbibliothek: https://github.com/scala/scala-continuations

Syntax

  • reset {...} // Fortsetzungen erstrecken sich bis zum Ende des umschließenden Rücksetzblocks
  • shift {...} // Erstellen Sie eine Fortsetzung, die aus dem Aufruf hervorgeht, und leitet sie an die Schließung weiter
  • A @cpsParam [B, C] // Eine Berechnung, die eine Funktion A => B erfordert, um einen Wert von C zu erstellen
  • @cps [A] // Alias ​​für @cpsParam [A, A]
  • @suspendable // Alias ​​für @cpsParam [Einheit, Einheit]

Bemerkungen

shift und reset sind einfache Kontrollflussstrukturen, wie Int.+ eine primitive Operation und Long ein primitiver Typ. Sie sind primitiver als beides, indem mit begrenzten Fortsetzungen tatsächlich fast alle Kontrollflussstrukturen erstellt werden können. Sie sind nicht sehr nützlich "out-of-the-box", aber sie leuchten wirklich, wenn sie in Bibliotheken zum Erstellen von umfangreichen APIs verwendet werden.

Fortsetzungen und Monaden sind ebenfalls eng miteinander verbunden. Es können Fortsetzungen in die Fortsetzungsmonade aufgenommen werden , und Monaden sind Fortsetzungen, da ihre flatMap Operation eine Fortsetzung als Parameter flatMap .

Rückrufe sind Kontinuationen

// 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)
  }}
}}

Das Funktionsargument für readFile ist eine Fortsetzung, indem readFile aufruft, um die Programmausführung fortzusetzen, nachdem es seine Aufgabe readFile hat.

Um zu kontrollieren, was leicht zur Callback-Hölle werden kann, verwenden wir die Fortsetzungsbibliothek.

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!

Erstellen von Funktionen, die Fortsetzung benötigen

Wird die shift außerhalb eines begrenzenden reset aufgerufen, können mit ihr Funktionen erstellt werden, die selbst Fortsetzungen innerhalb eines reset erzeugen. Es ist wichtig zu wissen, dass der shift -Typ nicht nur (((A => B) => C) => A) , sondern (((A => B) => C) => (A @cpsParam[B, C])) . Diese Anmerkung markiert, wo CPS-Transformationen benötigt werden. Funktionen, die die shift Funktion ohne reset aufrufen shift haben ihren Rückgabetyp mit dieser Anmerkung "infiziert".

Innerhalb eines reset scheint ein Wert von A @cpsParam[B, C] einen Wert von A , obwohl es wirklich nur so ist. Die Fortsetzung, die zum Abschließen der Berechnung erforderlich ist, hat den Typ A => B Der Code, der einer Methode folgt, die diesen Typ zurückgibt, muss B . C ist der "echte" Rückgabetyp, und nach der CPS-Umwandlung hat der Funktionsaufruf den Typ C

Nun das Beispiel aus dem Scaladoc der Bibliothek

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)
}

Hier speichert ask die Fortsetzung in einer Map, und später kann ein anderer Code diese "Sitzung" abrufen und das Ergebnis der Abfrage an den Benutzer weiterleiten. Auf diese Weise kann go tatsächlich eine asynchrone Bibliothek verwenden, während der Code wie normaler imperativer Code aussieht.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow