С помощью call/cc можно выйти из вложенных вызовов и вернуть результат внешнему вызову. Используйте dynamic-wind, чтобы выполнить очистку при таком выходе.
Точка выхода, которую call/cc передаёт вашей процедуре, называется продолжением.
Запустите каждый пример целиком на площадке. Для обычного выбора используйте if или cond. Если вспомогательная процедура завершается нормально, достаточно вернуть её результат.
Выйдите до следующего выражения
(call/cc (lambda (escape) (escape 'done) 'unreachable))Результат
doneПроследите три шага.
call/ccвызываетlambda, передав ей процедуру выхода. Параметрescapeзадаёт имя этой процедуры.(escape 'done)прекращает оставшуюся работу лямбды и заставляет внешнийcall/ccвернутьdone.- Выражение
'unreachableпропускается. Выполнение продолжается послеcall/cc.
Продолжение — это работа, оставшаяся после завершения вычисления. Здесь escape представляет продолжение внешнего call/cc. Вызов этой процедуры выводит выполнение из вызовов внутри этого call/cc. Это называется выходом вверх.
Лиспекс позволяет выполнить такой выход один раз, пока внешний call/cc ещё активен. Процедура выхода помечается использованной до начала очистки. Следующий вызов выдаёт E340, даже если очистка ещё выполняется.
Выполните вход, основную работу и очистку
(dynamic-wind
(lambda () (display "before "))
(lambda () (display "during "))
(lambda () (display "after")))Вывод
before during afterdynamic-wind принимает три процедуры, которые вызываются без аргументов.
| Процедура | Когда выполняется |
|---|---|
before | При входе |
thunk | После before, для основной работы |
after | После основной работы, в том числе при ошибке или выходе из неё |
Thunk — это процедура, вызываемая без аргументов. При нормальном завершении dynamic-wind возвращает значения основной процедуры. Значения, возвращённые before и after, отбрасываются.
Проследите очистку при выходе
(write
(call/cc
(lambda (escape)
(dynamic-wind
(lambda () (display "before "))
(lambda ()
(display "during ")
(escape 'done)
(display "unreachable "))
(lambda () (display "after "))))))
(newline)Вывод
before during after doneВывод показывает порядок выполнения.
beforeпечатаетbefore.- Основная процедура печатает
duringи вызываетescape. - Оставшийся вызов
displayпропускается. Процедураafterпечатаетafter. call/ccвозвращаетdone. Внешнийwriteпечатает результат, аnewlineзавершает строку.
Поместите очистку в after. Она выполнится и при ошибке или выходе из основной процедуры. При выходе из нескольких вложенных dynamic-wind очистка идёт от самого внутреннего вызова к внешнему.
Если очистка вызывает новую ошибку или другую активную процедуру выхода, новая ошибка или выход заменяет уже обрабатываемый. Повторный вызов использованной escape из очистки выдаёт E340.
Вызывайте выход, пока его вызов ещё активен
(let ((saved #f))
(call/cc (lambda (escape) (set! saved escape) 'ready))
(saved 'late))Диагностика
E340 — escape continuation is no longer activeЗдесь call/cc нормально возвращает ready. Этот промежуточный результат отбрасывается. Последующий вызов saved завершается ошибкой, потому что внешний call/cc уже закончился. Это верно, даже если процедура выхода ни разу не вызывалась.
То же правило запрещает повторный вход в завершённое вычисление. before выполняется при входе и не запускается заново при выходе. Для повторения работы используйте цикл или обычный вызов процедуры.
Лиспекс передаёт сигналы ошибок и выхода через вычислитель к внешнему вызову. Эти сигналы отделены от исключений и продолжений языка вызывающего приложения.
Обычный выбор описан в разделе об условиях, а чтение ошибок — в разделе о диагностике.