Scala Language
계속 도서관
수색…
소개
연속 전달 스타일은 "연속"인수로 나머지 계산을 함수에 전달하는 것을 포함하는 제어 흐름의 한 형태입니다. 문제의 함수는 나중에 프로그램 실행을 계속하기 위해 해당 연속을 호출합니다. 연속체를 생각하는 한 가지 방법은 종료점입니다. 스칼라 연속 라이브러리는 언어로의 프리미티브 shift
/ reset
형식으로 구분 된 연속성을 제공합니다.
통사론
- reset {...} // 계속은 둘러싸는 리셋 블록의 끝까지 확장됩니다
- shift {...} // 호출 이후에 클로저에 전달하는 연속을 만듭니다.
- A @cpsParam [B, C] // C의 값을 만들기 위해 A => B 함수가 필요한 계산
- @cps [A] // @cpsParam의 별칭 [A, A]
- @suspendable // @cpsParam의 별칭 [Unit, Unit]
비고
shift
및 reset
은 Int.+
와 같은 기본 컨트롤 흐름 구조이며 기본 동작이며 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
는 실제로 비동기 라이브러리를 사용할 수 있지만 코드는 일반적인 명령형 코드처럼 보입니다.