수색…


소개

연속 전달 스타일은 "연속"인수로 나머지 계산을 함수에 전달하는 것을 포함하는 제어 흐름의 한 형태입니다. 문제의 함수는 나중에 프로그램 실행을 계속하기 위해 해당 연속을 호출합니다. 연속체를 생각하는 한 가지 방법은 종료점입니다. 스칼라 연속 라이브러리는 언어로의 프리미티브 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]

비고

shiftresetInt.+ 와 같은 기본 컨트롤 흐름 구조이며 기본 동작이며 Long 은 기본 유형입니다. 이들은 구분 된 연속이 실제로 거의 모든 제어 흐름 구조를 구성하는 데 실제로 사용될 수 있다는 점에서 둘 다보다 원시적입니다. "즉시 사용할 수있는"기능은별로 유용하지 않지만 풍부한 API를 만들기 위해 라이브러리에서 사용될 때 진정으로 빛을 발합니다.

연속체와 모나드도 밀접하게 연결되어 있습니다. 연속 모나드계속할 수 있으며, 모나드는 flatMap 작업이 매개 변수로 연속을 사용하기 때문에 연속입니다.

콜백은 Continutations입니다.

// 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 호출한다는 점에서 연속입니다.

쉽게 콜백 지옥이 될 수있는 것을 억제하기 위해 우리는 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!

계속되는 함수 만들기

경우 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 이므로이 유형을 리턴하는 메소드 다음의 코드는 B 를 리턴해야합니다. C 는 "실제"반환 유형이며 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