Преобразуете ли вы реализацию пользовательского кода в стиль передачи продолжения?В таком случае это легко peasy.call/cc
это:
(define (call/cc& f& continuation)
(define (exit& value actual-continuation)
(continuation value))
(f& exit& continuation))
Глядя на ваш первый код, я думаю, что это выглядит примерно так:
((lambda (x k)
((lambda (f k)
(call/cc& f (lambda (v) ; continuation a
(display& v (lambda (_) ; continuation b
(x "30" k))))))
(lambda (r k)
(set!& x r (lambda (_) ; continuation c
(r 20 (lambda (v) ; continuation d
(display& v (lambda (_) ; continuation e
(display& "10" k))))))))
k)
0
halt)
Вот что происходит:
- Мы делаем
x
и f
call/cc&
звонков f
x
установлен на r
(продолжение а) - r получаетвызывается с 20 как значение
- продолжение c игнорируется, вместо этого отображается продолжение a с 20
- 20, затем продолжение b вызывается
- b с
x
«30» - продолжение k игнорируется, вместо этого вызывается продолжение a с 30
- 30, затем вызывается продолжение b
- перейти к вызовам b
x
с «30» на 3 строки вверх и продолжайте
Поэтому выведите «20», тогда «30» навсегда будет правильным результатом для этого кода. Важно отметить, что он никогда не будет display "10"
, поскольку он вызывает r
и передает продолжение, но оно обходится до call/cc
оригинального продолжения, которое является продолжениемВступление а.
Что касается реализаций.Раньше для всех реализаций Scheme было обычным делом просто преобразовывать код в стиль передачи продолжения, но сегодня более распространено делать только те части, которые требуются.Например.Ikarus не выполняет CPS, но для того, чтобы call/cc
работал, он должен делать это до следующего приглашения на продолжение.
Вероятно, лучше смотреть на call/cc
вначале без мутаций.например.
(+ 2 (call/cc (lambda (exit)
(+ 3 (* 5 (exit 11))))))
Теперь это превращается в:
(call/cc& (lambda (exit k)
(exit 11 (lambda (v)
(*& 5 v (lambda (v)
(+& 3 v k))))))
(lambda (v)
(+& 2 v repl-display)))
Теперь мы знаем, что exit
вызывается, и, таким образом, все это превращается в:
((lambda (v) (+& 2 v repl-display)) 11)
Что отображает13
.Теперь наличие продолжения в качестве последнего аргумента выглядит хорошо на бумаге.В реализации, которая хочет поддерживать varargs, вероятно, лучше, чтобы продолжение было первым аргументом.
Все продолжения являются хвостовыми вызовами, и поэтому стек никогда не увеличивается.Фактически, если используется полный CPS, вам никогда не придется возвращаться.Все интересное всегда передается до следующего вызова, пока программа не остановится.