Tôi thấy phần này khó hiểu khi đọc lần đầu tiên và chỉ bắt đầu đọc sau khi tôi đọc ở nơi khác về tiếp tục và kiểu chuyển tiếp liên tục (đây là những gì đây).
Có nguy cơ giải thích điều gì đó mà bạn đã nhận được, một cách nhìn vào nó giúp tôi nghĩ đến "người sưu tập" hoặc "tiếp tục" thay thế cách thông thường cho hàm trả về giá trị. Trong phong cách lập trình thông thường, bạn gọi một hàm, nhận một giá trị và làm điều gì đó với nó trong người gọi. Ví dụ, hàm đệ quy tiêu chuẩn length
bao gồm cụm từ (+ 1 (length (cdr list)))
cho trường hợp không trống. Điều đó có nghĩa là khi một lần (length (cdr list))
trả về một giá trị, có một tính toán đang chờ xảy ra với bất kỳ giá trị nào nó tạo ra, mà chúng ta có thể nghĩ là (+ 1 [returned value])
. Trong chương trình bình thường, trình thông dịch theo dõi các tính toán đang chờ xử lý này, có xu hướng "xếp chồng lên nhau", như bạn có thể thấy trong vài chương đầu tiên của cuốn sách. Ví dụ, khi tính toán độ dài của một danh sách đệ quy, chúng ta có một tổ "tính toán chờ đợi" như nhiều cấp độ sâu khi danh sách dài.
Trong kiểu chuyển tiếp liên tục, thay vì gọi hàm và sử dụng kết quả trả về trong hàm gọi, chúng tôi cho biết chức năng cần làm khi tạo giá trị bằng cách cung cấp chức năng "tiếp tục" để gọi. (Điều này tương tự như những gì bạn phải làm với các cuộc gọi lại trong lập trình Javascript không đồng bộ, ví dụ: thay vì viết result = someFunction();
bạn viết someFunction(function (result) { ... })
và tất cả mã sử dụng result
đều nằm trong hàm gọi lại).
Đây là length
theo kiểu chuyển tiếp liên tục, chỉ để so sánh. Tôi đã gọi tham số tiếp tục return
, nên gợi ý cách nó hoạt động ở đây, nhưng hãy nhớ rằng nó chỉ là một biến Scheme bình thường giống như bất kỳ biến nào khác. (Thông thường, tham số tiếp tục được gọi là k
theo kiểu này).
(define (length/k lis return)
(cond ((null? lis) (return 0))
(else
(length/k (cdr lis)
(lambda (cdr-len)
(return (+ cdr-len 1)))))))
Có một mẹo hữu ích để đọc loại mã này trong an article on continuations by Little Schemer co-author Dan Friedman. (Xem phần II-5 bắt đầu ở trang 8).Diễn giải, đây là những gì các khoản else
trên nói:
tưởng tượng bạn có kết quả của gọi length/k
trên (cdr lis)
, và gọi nó cdr-len
, sau đó thêm một và vượt qua các kết quả bổ sung này để tiếp tục của bạn (return
) .
Lưu ý rằng đây là gần như chính xác những gì người phiên dịch đã làm trong việc đánh giá (+ 1 (length (cdr lis)))
trong phiên bản bình thường của chức năng (ngoại trừ việc nó không nhất thiết phải đặt tên cho kết quả trung gian (length (cdr lis))
. Bằng cách vượt qua xung quanh tiếp tục hoặc gọi lại chúng tôi đã thực hiện dòng điều khiển (và tên của các giá trị trung gian) rõ ràng, thay vì yêu cầu thông dịch viên theo dõi nó. thực tế là hàm này tạo ra ba giá trị thay vì một: danh sách lồng nhau với số lẻ đã xóa; sản phẩm của các số chẵn; và tổng các số lẻ. Đây là khoản đầu tiên, nơi (car l)
được biết đến là một số chẵn:
(evens-only*&co (cdr l)
(lambda (newl product sum)
(col (cons (car l) newl)
(opx (car l) product)
sum)))
Hãy tưởng tượng rằng bạn có kết quả loại bỏ số lẻ, SỐ CHẴN nhân, và thêm số lẻ từ cdr
của danh sách, và gọi cho chúng lần lượt là newl
, product
và sum
. cons
số đứng đầu danh sách lên newl
(vì đó là số chẵn, nên đi trong kết quả); nhân product
bởi người đứng đầu danh sách (kể từ chúng tôi đang tính toán sản phẩm evens); để lại sum
một mình; và vượt qua các giá trị ba này để tiếp tục chờ đợi của bạn col
.
Đây là trường hợp người đứng đầu danh sách này là một số lẻ:
(evens-only*&co (cdr l)
(lambda (newl product sum)
(col newl product (op+ (car l) sum))))
Như trước đây, nhưng vượt qua cùng các giá trị của newl
và product
để tiếp tục (tức là "trở lại" họ), cùng với số tiền của sum
và người đứng đầu danh sách, vì chúng tôi tổng hợp các số lẻ.
Và đây là người cuối cùng, nơi (car l)
là danh sách lồng nhau, và đó là hơi phức tạp do đệ quy kép:
(evens-only*&co (car l)
(lambda (newl product sum)
(evens-only*&co (cdr l)
(lambda (dnewl dproduct dsum)
(col (cons newl dnewl)
(opx product dproduct)
(op+ sum dsum))))))
Hãy tưởng tượng bạn có kết quả từ loại bỏ, tổng hợp và thêm các số trong (car l)
và gọi những số này newl
, product
và sum
; sau đó hãy tưởng tượng bạn có kết quả từ làm điều tương tự với (cdr l)
, và gọi cho họ dnewl
, dproduct
và dsum
.Để chờ đợi sự tiếp tục của bạn , hãy cung cấp các giá trị được sản xuất bởi cons
ing newl
và dnewl
(vì chúng tôi đang tạo danh sách danh sách); nhân với nhau product
và dproduct
; và thêm sum
và dsum
.
Chú ý: mỗi lần chúng tôi thực hiện cuộc gọi đệ quy, chúng ta xây dựng một sự tiếp nối mới cho cuộc gọi đệ quy, mà "đóng cửa trên" các giá trị hiện tại của các đối số, l
, và việc tiếp tục trở lại - col
, nói cách khác , bạn có thể nghĩ về chuỗi liên tục mà chúng ta xây dựng trong suốt quá trình đệ quy như mô hình hóa "ngăn xếp cuộc gọi" của một hàm được viết thông thường hơn!
Hy vọng rằng sẽ đưa ra một phần câu trả lời cho câu hỏi của bạn. Nếu tôi đã đi quá ít, chỉ vì tôi nghĩ rằng, sau khi đệ quy chính nó, tiếp tục là ý tưởng thực sự gọn gàng, cởi mở thứ hai trong The Little Schemer và lập trình nói chung.
Để thiết lập trình điều khiển, hãy xem http://stackoverflow.com/questions/10499514/y-combinator-discussion-in-the-little-schemer/10500358#10500358 – soegaard