의미있는 예제를 통한 스칼라 연속
from0to10
0에서 10까지 반복의 개념을 표현 하는 것을 정의합시다 .
def from0to10() = shift { (cont: Int => Unit) =>
for ( i <- 0 to 10 ) {
cont(i)
}
}
지금,
reset {
val x = from0to10()
print(s"$x ")
}
println()
인쇄물:
0 1 2 3 4 5 6 7 8 9 10
실제로 다음은 필요하지 않습니다 x
.
reset {
print(s"${from0to10()} ")
}
println()
동일한 결과를 인쇄합니다.
과
reset {
print(s"(${from0to10()},${from0to10()}) ")
}
println()
모든 쌍을 인쇄합니다.
(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9) (0,10) (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,8) (1,9) (1,10) (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,8) (2,9) (2,10) (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (3,8) (3,9) (3,10) (4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (4,8) (4,9) (4,10) (5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,8) (5,9) (5,10) (6,0) (6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7) (6,8) (6,9) (6,10) (7,0) (7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7) (7,8) (7,9) (7,10) (8,0) (8,1) (8,2) (8,3) (8,4) (8,5) (8,6) (8,7) (8,8) (8,9) (8,10) (9,0) (9,1) (9,2) (9,3) (9,4) (9,5) (9,6) (9,7) (9,8) (9,9) (9,10) (10,0) (10,1) (10,2) (10,3) (10,4) (10,5) (10,6) (10,7) (10,8) (10,9) (10,10)
자, 어떻게 작동합니까?
있다 라는 코드 , from0to10
및 호출 코드 . 이 경우 뒤에 오는 블록입니다 reset
. 호출 된 코드에 전달 된 매개 변수 중 하나는 호출 코드의 어떤 부분이 아직 실행되지 않았는지 보여주는 반환 주소입니다 (**). 호출 코드의 해당 부분은 연속 입니다. 호출 된 코드는 해당 매개 변수로 제어를 전달하거나 무시하거나 여러 번 호출 할 수 있습니다. 여기서는 from0to10
0..10 범위의 각 정수에 대한 연속을 호출합니다.
def from0to10() = shift { (cont: Int => Unit) =>
for ( i <- 0 to 10 ) {
cont(i)
}
}
그러나 계속은 어디에서 끝날까요? return
연속 의 마지막 이 호출 된 코드로 제어를 반환 하기 때문에 이것은 중요 합니다 from0to10
. Scala에서는 reset
블록이 끝나는 곳 (*)에서 끝납니다.
이제 연속이 cont: Int => Unit
. 왜? 우리는 호출 from0to10
로 val x = from0to10()
, 그리고 Int
에 간다 값의 유형이다 x
. Unit
이후 블록 reset
은 값을 반환하지 않아야 함을 의미합니다 (그렇지 않으면 유형 오류가 발생 함). 일반적으로 함수 입력, 연속 입력, 연속 결과, 함수 결과의 4 가지 타입 시그니처가 있습니다. 네 가지 모두 호출 컨텍스트와 일치해야합니다.
위에서 우리는 값 쌍을 인쇄했습니다. 곱셈표를 인쇄 해 보겠습니다. 그러나 \n
각 행 후에 어떻게 출력 합니까?
이 함수를 back
사용하면 제어가 다시 호출 될 때 수행해야하는 작업을 연속에서 호출 한 코드까지 지정할 수 있습니다.
def back(action: => Unit) = shift { (cont: Unit => Unit) =>
cont()
action
}
back
먼저 연속을 호출 한 다음 작업 을 수행합니다 .
reset {
val i = from0to10()
back { println() }
val j = from0to10
print(f"${i*j}%4d ")
}
다음을 인쇄합니다.
0 0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9 10
0 2 4 6 8 10 12 14 16 18 20
0 3 6 9 12 15 18 21 24 27 30
0 4 8 12 16 20 24 28 32 36 40
0 5 10 15 20 25 30 35 40 45 50
0 6 12 18 24 30 36 42 48 54 60
0 7 14 21 28 35 42 49 56 63 70
0 8 16 24 32 40 48 56 64 72 80
0 9 18 27 36 45 54 63 72 81 90
0 10 20 30 40 50 60 70 80 90 100
글쎄, 이제는 두뇌 트위스터를 할 때입니다. 의 두 가지 호출이 from0to10
있습니다. 첫 번째 연속은 무엇입니까 from0to10
? 그것은의 호출은 다음과 from0to10
에서 바이너리 코드를 하지만, 소스 코드에서 또한 할당 문을 포함한다 val i =
. 그것은 reset
블록 이 끝나는 곳에서 끝나지만 블록의 끝은 reset
제어를 첫 번째 from0to10
. 의 최종 reset
블록 반환 2에 제어를 from0to10
차례로 결국 제어를 반환, back
하고는 back
반환의 첫 번째 호출을 제어하는 from0to10
. 첫 번째 (예! 첫 번째!)가 from0to10
종료되면 전체 reset
블록이 종료됩니다.
제어를 되 돌리는 이러한 방법을 역 추적 이라고하며 , 이는 적어도 Prolog 및 AI 지향 Lisp 파생물 시대부터 알려진 매우 오래된 기술입니다.
이름 reset
과 shift
잘못된 이름입니다. 이러한 이름은 비트 연산을 위해 남겨 두는 것이 좋습니다. reset
연속 경계를 정의 shift
하고 호출 스택에서 연속을 가져옵니다.
메모)
(*) Scala에서 연속은 reset
블록이 끝나는 곳에서 끝납니다. 또 다른 가능한 접근 방식은 함수가 끝나는 곳에서 끝나도록하는 것입니다.
(**) 호출 된 코드의 매개 변수 중 하나는 호출 코드의 어떤 부분이 아직 실행되지 않았는지 보여주는 반환 주소입니다. 음, Scala에서는 일련의 반환 주소가 사용됩니다. 얼마나? reset
블록에 들어간 이후 호출 스택에있는 모든 반환 주소 .
UPD 파트 2
연속 삭제 : 필터링
def onEven(x:Int) = shift { (cont: Unit => Unit) =>
if ((x&1)==0) {
cont()
}
}
reset {
back { println() }
val x = from0to10()
onEven(x)
print(s"$x ")
}
이것은 다음을 인쇄합니다.
0 2 4 6 8 10
두 가지 중요한 작업을 고려해 보겠습니다. 연속을 버리고 ( fail()
) 제어를 전달 ( succ()
)합니다.
def fail() = shift { (cont: Unit => Unit) => }
def succ():Unit @cpsParam[Unit,Unit] = { }
succ()
(위) 의 두 버전 모두 작동합니다. shift
재미있는 서명이있는 것으로 밝혀졌고 succ()
아무것도하지 않지만 유형 균형을 위해 해당 서명이 있어야합니다.
reset {
back { println() }
val x = from0to10()
if ((x&1)==0) {
succ()
} else {
fail()
}
print(s"$x ")
}
예상대로 인쇄됩니다.
0 2 4 6 8 10
함수 내에서 succ()
필요하지 않습니다.
def onTrue(b:Boolean) = {
if(!b) {
fail()
}
}
reset {
back { println() }
val x = from0to10()
onTrue ((x&1)==0)
print(s"$x ")
}
다시, 그것은 인쇄
0 2 4 6 8 10
이제 다음을 onOdd()
통해 정의하겠습니다 onEven()
.
class ControlTransferException extends Exception {}
def onOdd(x:Int) = shift { (cont: Unit => Unit) =>
try {
reset {
onEven(x)
throw new ControlTransferException()
}
cont()
} catch {
case e: ControlTransferException =>
case t: Throwable => throw t
}
}
reset {
back { println() }
val x = from0to10()
onOdd(x)
print(s"$x ")
}
위에서 x
짝수이면 예외가 발생하고 연속이 호출되지 않습니다. 경우 x
홀수 예외가 발생되지 않고 계속 호출됩니다. 위의 코드는 다음을 인쇄합니다.
1 3 5 7 9