Песочница

Досрочный выход из вычисления

Выходите из вложенных вызовов через call/cc и проследите очистку в dynamic-wind.

С помощью call/cc можно выйти из вложенных вызовов и вернуть результат внешнему вызову. Используйте dynamic-wind, чтобы выполнить очистку при таком выходе. Точка выхода, которую call/cc передаёт вашей процедуре, называется продолжением.

Запустите каждый пример целиком на площадке. Для обычного выбора используйте if или cond. Если вспомогательная процедура завершается нормально, достаточно вернуть её результат.

Выйдите до следующего выражения

LISPEX
(call/cc (lambda (escape) (escape 'done) 'unreachable))

Результат

ВЫВОД
done

Проследите три шага.

  1. call/cc вызывает lambda, передав ей процедуру выхода. Параметр escape задаёт имя этой процедуры.
  2. (escape 'done) прекращает оставшуюся работу лямбды и заставляет внешний call/cc вернуть done.
  3. Выражение 'unreachable пропускается. Выполнение продолжается после call/cc.

Продолжение — это работа, оставшаяся после завершения вычисления. Здесь escape представляет продолжение внешнего call/cc. Вызов этой процедуры выводит выполнение из вызовов внутри этого call/cc. Это называется выходом вверх.

Лиспекс позволяет выполнить такой выход один раз, пока внешний call/cc ещё активен. Процедура выхода помечается использованной до начала очистки. Следующий вызов выдаёт E340, даже если очистка ещё выполняется.

Выполните вход, основную работу и очистку

LISPEX
(dynamic-wind
  (lambda () (display "before "))
  (lambda () (display "during "))
  (lambda () (display "after")))

Вывод

ВЫВОД
before during after

dynamic-wind принимает три процедуры, которые вызываются без аргументов.

ПроцедураКогда выполняется
beforeПри входе
thunkПосле before, для основной работы
afterПосле основной работы, в том числе при ошибке или выходе из неё

Thunk — это процедура, вызываемая без аргументов. При нормальном завершении dynamic-wind возвращает значения основной процедуры. Значения, возвращённые before и after, отбрасываются.

Проследите очистку при выходе

LISPEX
(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

Вывод показывает порядок выполнения.

  1. before печатает before .
  2. Основная процедура печатает during и вызывает escape.
  3. Оставшийся вызов display пропускается. Процедура after печатает after .
  4. call/cc возвращает done. Внешний write печатает результат, а newline завершает строку.

Поместите очистку в after. Она выполнится и при ошибке или выходе из основной процедуры. При выходе из нескольких вложенных dynamic-wind очистка идёт от самого внутреннего вызова к внешнему.

Если очистка вызывает новую ошибку или другую активную процедуру выхода, новая ошибка или выход заменяет уже обрабатываемый. Повторный вызов использованной escape из очистки выдаёт E340.

Вызывайте выход, пока его вызов ещё активен

LISPEX
(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 выполняется при входе и не запускается заново при выходе. Для повторения работы используйте цикл или обычный вызов процедуры.

Лиспекс передаёт сигналы ошибок и выхода через вычислитель к внешнему вызову. Эти сигналы отделены от исключений и продолжений языка вызывающего приложения.

Обычный выбор описан в разделе об условиях, а чтение ошибок — в разделе о диагностике.