Recherche…


Introduction

Le style de passage de continuation est une forme de flux de contrôle qui consiste à passer à des fonctions le reste du calcul en tant qu'argument de "continuation". La fonction en question invoque ultérieurement cette continuation pour continuer l'exécution du programme. Une façon de penser à une continuation est une fermeture. La bibliothèque de suites Scala apporte des continuations délimitées sous la forme des primitives shift / reset à la langue.

bibliothèque de continuations: https://github.com/scala/scala-continuations

Syntaxe

  • reset {...} // Les prolongements se prolongent jusqu'à la fin du bloc de réinitialisation
  • shift {...} // Crée une suite indiquant après l'appel, en la passant à la fermeture
  • Un @cpsParam [B, C] // Un calcul qui nécessite une fonction A => B pour créer une valeur de C
  • @ cps [A] // Alias ​​pour @cpsParam [A, A]
  • @suspendable // Alias ​​pour @cpsParam [Unit, Unit]

Remarques

shift et reset sont des structures de flux de contrôle primitives, comme Int.+ est une opération primitive et Long est un type primitif. Ils sont plus primitifs que l'un ou l'autre dans le sens où les continuations délimitées peuvent réellement être utilisées pour construire presque toutes les structures de flux de contrôle. Ils ne sont pas très utiles "prêts à l'emploi", mais ils brillent vraiment lorsqu'ils sont utilisés dans des bibliothèques pour créer des API riches.

Les continuations et les monades sont également étroitement liées. Des continuations peuvent être faites dans la monade de continuation , et les monades sont des continuations car leur opération flatMap prend une suite en paramètre.

Les rappels sont des continuations

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

L'argument function de readFile est une continuation, en ce readFile que readFile invoque pour continuer l'exécution du programme après avoir fait son travail.

Afin de limiter ce qui peut facilement devenir un rappel, nous utilisons la bibliothèque de continuations.

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!

Créer des fonctions qui prennent des continuités

Si shift est appelé en dehors d'un bloc de reset délimitation, il peut être utilisé pour créer des fonctions qui créent elles-mêmes des continuations dans un bloc de reset . Il est important de noter que le type de shift n'est pas juste (((A => B) => C) => A) , il est en fait (((A => B) => C) => (A @cpsParam[B, C])) . Les annotations indiquent où les transformations CPS sont nécessaires. Les fonctions appelant shift sans reset ont leur type de retour "infecté" avec cette annotation.

À l'intérieur d'un bloc de reset , la valeur A @cpsParam[B, C] semble avoir la valeur A , bien que ce ne soit que de la simulation. La suite nécessaire pour terminer le calcul a le type A => B , donc le code suivant une méthode qui renvoie ce type doit renvoyer B C est le type de retour "réel", et après la transformation CPS, l'appel de fonction a le type C

Maintenant, l'exemple tiré du Scaladoc de la bibliothèque

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

Ici, ask stockera la suite dans une map, et plus tard, un autre code pourra récupérer cette "session" et transmettre le résultat de la requête à l'utilisateur. De cette façon, go peut réellement utiliser une bibliothèque asynchrone alors que son code ressemble à un code impératif normal.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow