Tiếp tục Scala qua các ví dụ có ý nghĩa
Hãy để chúng tôi xác định from0to10
thể hiện ý tưởng lặp lại từ 0 đến 10:
def from0to10() = shift { (cont: Int => Unit) =>
for ( i <- 0 to 10 ) {
cont(i)
}
}
Hiện nay,
reset {
val x = from0to10()
print(s"$x ")
}
println()
bản in:
0 1 2 3 4 5 6 7 8 9 10
Trên thực tế, chúng ta không cần x
:
reset {
print(s"${from0to10()} ")
}
println()
in ra cùng một kết quả.
Và
reset {
print(s"(${from0to10()},${from0to10()}) ")
}
println()
in tất cả các cặp:
(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)
Bây giờ, nó hoạt động như thế nào?
Có các mã được gọi là , from0to10
và mã gọi . Trong trường hợp này, nó là khối theo sau reset
. Một trong các tham số được truyền cho mã được gọi là địa chỉ trả về cho biết phần nào của mã gọi vẫn chưa được thực thi (**). Đó là một phần của mã gọi là sự tiếp nối . Mã được gọi có thể làm với tham số đó bất cứ điều gì nó quyết định: chuyển quyền điều khiển cho nó, hoặc bỏ qua hoặc gọi nó nhiều lần. Ở đây from0to10
gọi sự tiếp tục đó cho mỗi số nguyên trong phạm vi 0..10.
def from0to10() = shift { (cont: Int => Unit) =>
for ( i <- 0 to 10 ) {
cont(i)
}
}
Nhưng sự tiếp tục kết thúc ở đâu? Điều này rất quan trọng vì cuối cùng return
từ phần tiếp diễn trả lại quyền điều khiển cho mã được gọi from0to10
,. Trong Scala, nó kết thúc khi reset
khối kết thúc (*).
Bây giờ, chúng ta thấy rằng phần tiếp diễn được khai báo là cont: Int => Unit
. Tại sao? Chúng tôi gọi from0to10
là val x = from0to10()
, và Int
là loại giá trị đi đến x
. Unit
có nghĩa là khối sau reset
phải trả về không có giá trị nào (nếu không sẽ có lỗi kiểu). Nói chung, có 4 loại chữ ký: đầu vào hàm, đầu vào tiếp tục, kết quả tiếp tục, kết quả hàm. Cả bốn phải phù hợp với ngữ cảnh gọi.
Ở trên, chúng tôi đã in các cặp giá trị. Hãy để chúng tôi in bảng cửu chương. Nhưng làm thế nào để chúng ta xuất ra \n
sau mỗi hàng?
Hàm back
cho phép chúng tôi chỉ định những gì phải thực hiện khi điều khiển quay trở lại, từ phần tiếp tục đến mã đã gọi nó.
def back(action: => Unit) = shift { (cont: Unit => Unit) =>
cont()
action
}
back
đầu tiên gọi sự tiếp tục của nó và sau đó thực hiện hành động .
reset {
val i = from0to10()
back { println() }
val j = from0to10
print(f"${i*j}%4d ")
}
Nó in:
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
Chà, bây giờ là lúc cho một số người điên đầu. Có hai cách gọi from0to10
. Phần tiếp theo cho phần đầu tiên là from0to10
gì? Nó tuân theo lệnh gọi from0to10
trong mã nhị phân , nhưng trong mã nguồn nó cũng bao gồm câu lệnh gán val i =
. Nó kết thúc khi reset
khối kết thúc, nhưng phần cuối của reset
khối không trả lại quyền kiểm soát cho khối đầu tiên from0to10
. Phần cuối của reset
khối trả lại quyền kiểm soát cho khối thứ 2 from0to10
, đến lượt nó cuối cùng trả lại quyền kiểm soát back
và nó back
trả lại quyền kiểm soát cho lệnh gọi đầu tiên from0to10
. Khi khối đầu tiên (có! 1!) from0to10
Thoát ra, toàn bộ reset
khối sẽ được thoát.
Phương pháp trả lại quyền điều khiển như vậy được gọi là backtracking , nó là một kỹ thuật rất cũ, được biết đến ít nhất là từ thời Prolog và các dẫn xuất Lisp hướng AI.
Tên reset
và shift
là những người viết sai. Những tên này tốt hơn nên được để lại cho các hoạt động bitwise. reset
xác định ranh giới tiếp tục và shift
tiếp tục từ ngăn xếp cuộc gọi.
Ghi chú
(*) Trong Scala, phần tiếp diễn kết thúc khi reset
khối kết thúc. Một cách tiếp cận khác có thể là để nó kết thúc khi hàm kết thúc.
(**) Một trong các tham số của mã được gọi là địa chỉ trả về cho biết phần nào của mã gọi chưa được thực thi. Vâng, trong Scala, một chuỗi các địa chỉ trả về được sử dụng cho việc đó. Bao nhiêu? Tất cả các địa chỉ trả về được đặt trên ngăn xếp cuộc gọi kể từ khi nhập reset
khối.
UPD Phần 2 Loại
bỏ tiếp tục: Lọc
def onEven(x:Int) = shift { (cont: Unit => Unit) =>
if ((x&1)==0) {
cont()
}
}
reset {
back { println() }
val x = from0to10()
onEven(x)
print(s"$x ")
}
Bản in này:
0 2 4 6 8 10
Hãy để chúng tôi xác định hai hoạt động quan trọng: loại bỏ tiếp tục ( fail()
) và chuyển quyền kiểm soát cho nó ( succ()
):
def fail() = shift { (cont: Unit => Unit) => }
def succ():Unit @cpsParam[Unit,Unit] = { }
Cả hai phiên bản của succ()
(ở trên) đều hoạt động. Nó chỉ ra rằng shift
có một chữ ký vui nhộn, và mặc dù succ()
không có gì, nó phải có chữ ký đó để cân bằng loại.
reset {
back { println() }
val x = from0to10()
if ((x&1)==0) {
succ()
} else {
fail()
}
print(s"$x ")
}
như mong đợi, nó in
0 2 4 6 8 10
Trong một hàm, succ()
không cần thiết:
def onTrue(b:Boolean) = {
if(!b) {
fail()
}
}
reset {
back { println() }
val x = from0to10()
onTrue ((x&1)==0)
print(s"$x ")
}
một lần nữa, nó in
0 2 4 6 8 10
Bây giờ, chúng ta hãy xác định onOdd()
thông qua 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 ")
}
Ở trên, nếu x
là chẵn, một ngoại lệ được ném ra và phần tiếp tục không được gọi; nếu x
là số lẻ, ngoại lệ không được ném ra và phần tiếp tục được gọi. Đoạn mã trên in ra:
1 3 5 7 9