ความต่อเนื่องของสกาล่าผ่านตัวอย่างที่มีความหมาย
ให้เรากำหนด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
ผลตอบแทนควบคุมการภาวนาแรกของเมื่อครั้งแรก (ใช่! 1!) from0to10
ออกทั้งหมดreset
บล็อกจะถูกออก
เรียกวิธีการคืนการควบคุมกลับดังกล่าว ย้อนรอยซึ่งเป็นเทคนิคที่เก่าแก่มากซึ่งเป็นที่รู้จักอย่างน้อยก็ในสมัยของ Prolog และอนุพันธ์ Lisp ที่เน้น AI
ชื่อreset
และการshift
เรียกชื่อผิด ชื่อเหล่านี้ควรถูกทิ้งไว้สำหรับการดำเนินการระดับบิต reset
กำหนดขอบเขตความต่อเนื่องและshift
รับช่วงต่อจาก call stack
หมายเหตุ (s)
(*) ใน 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