TL; DR call/cc
позволяет подключиться к внутреннему коду Schemes, чтобы вы могли использовать продолжения без написания кода в стиле передачи продолжения. Лучшая модель оценки - модель замещения, учитывая, что вы не используете set
и просматриваете ее из кода CPS
Представьте себе эту маленькую программу.
(define (sum-list lst)
(cond ((null? lst) 0)
((number? (car lst)) (+ (car lst) (sum-list (cdr lsr))))
(else (sum-list (cdr lsr)))))
(display (sum-list '(1 2 3 4))) ; displays 10
Представьте, что вы хотите, чтобы результат был 1337
, если вы достигнете условия else
. Как бы вы пошли на это, не переписывая все это? Ты не можешьОднако в большинстве реализаций Scheme преобразование CPS в ваш код выполняется там, где его изменение тривиально:
(define (sum-list& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst k))))))))))))))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
cond
- это макрос, поэтому вы видите только вызовы if&
. Я знаю, о чем ты думаешь. Почему бы не сказать программистам делать это в первую очередь так? Просто шучу!
Если вы измените второе продолжение во втором if&
на (lambda () (k 1337))
, все готово. Каким бы красивым ни был CPS, читать и писать ужасно, но так как компилятор делает это в любом случае, не может быть способа написать свой код первым способом и все же добраться до потока управления кодом? Лучшее из двух миров доступно с call/cc
. call/cc
это в CPS:
(define (call/cc& f& continuation)
(define (exit& value actual-continuation)
(continuation value))
(f& exit& continuation))
Это ничего не значит вообще. Он передает exit&
, который игнорирует реальное продолжение программы при вызове и вызывает значение с исходным продолжением вызова call/cc&
. Со списком '(1 2 3 #f)
у вас будет 3 вложенных продолжения, ожидающих добавления с результатом, но все это нужно отменить. Если вы выбираете значение продолжения до этого расчета, оно автоматически отменяется. И это все. Когда вы пишете свой код с ним, вам не нужно делать полный CPS, только то продолжение, которое вы хотели, показалось call/cc
следующим образом:
(define (sum-list lst)
(call/cc
(lambda (k)
(define (helper lst)
(cond ((null? lst) 0)
((number? (car lst))
(+ (car lst) (helper (cdr lst))))
(else (k 1337))))
(helper lst))))
Это преобразуется в CPS:
(define (sum-list& lst k)
(call/cc&
(lambda (k& real-k)
(define (helper& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(helper& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(k& 1337 real-k))))))))))))
(helper& lst real-k))
k))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
call/cc
всегда можно избежать. Наш пример можно было бы переписать так, чтобы он возвращал 1337 следующим образом:
(define (sum-list lst)
(define (helper lst sum)
(cond ((null? lst) sum)
((number? (car lst)) (helper (cdr lst) (+ sum (car lst))))
(else 1337)))
(helper lst 0))
Это хвостовая рекурсия, и вместо создания продолжений он накапливается. Для полноты вот его версия CPS:
(define (helper& lst sum k)
(null?& lst
(lambda (nlst)
(if& nlst
(lambda () (k sum))
(lambda ()
(car& lst
(lambda (cl)
(number?& cl
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (cdrl)
(+& sum
cl
(lambda (scl)
(helper& cdrl
scl
k))))))
(lambda () (k 1337))))))))))))
(define (sum-list& lst k)
(helper& lst 0 k))