catch и throwБольшинство конструкций управления влияют только на поток управления внутри
самой конструкции. Функция throw является исключением из этого
правила нормального выполнения программы: она выполняет нелокальный выход по
запросу. (Существуют и другие исключения, но они предназначены только для
обработки ошибок.) throw используется внутри catch
и возвращается к этому catch. Например:
(defun foo-outer ()
(catch 'foo
(foo-inner)))
(defun foo-inner ()
…
(if x
(throw 'foo t))
…)
Форма throw, если она выполняется, немедленно передает управление
обратно соответствующему catch, который немедленно возвращается.
Код, следующий за throw, не выполняется. Второй аргумент
throw используется в качестве возвращаемого значения catch.
Функция throw находит соответствующий catch,
основываясь на первом аргументе: она ищет catch, чей первый
аргумент eq с тем, что указано в throw. Если
применимо более одного catch, имеет приоритет самый внутренний.
Таким образом, в приведенном выше примере throw указывает
foo, а catch в foo-outer
указывает на ту же самую символу, поэтому catch является
применимым (при условии, что между ними нет другого соответствующего
catch).
Выполнение throw выходит из всех конструкций Lisp до соответствующего
catch, включая вызовы функций. Когда конструкции привязки, такие
как let, или вызовы функций завершаются таким образом,
привязки отменяются, как и при нормальном завершении этих конструкций (см.
Локальные переменные). Точно так же
throw восстанавливает буфер и позицию, сохраненные
save-excursion (см. Экскурсии),
и статус сужения, сохраненный save-restriction. Он также
выполняет любую очистку, установленную с помощью специальной формы
unwind-protect, когда она выходит из этой формы (см.
Очистка при нелокальных выходах).
Не обязательно, чтобы throw появлялся лексически внутри
catch, к которому он переходит. Его также можно вызвать из другой
функции, вызванной внутри catch. Пока throw
происходит хронологически после входа в catch и хронологически
до выхода из него, он имеет доступ к этому catch. Вот почему
throw может использоваться в таких командах, как
exit-recursive-edit, которые возвращаются в цикл команд редактора
(см. Рекурсивное редактирование).
Примечание Common Lisp: В большинстве других версий Lisp, включая Common Lisp, есть несколько способов непоследовательной передачи управления:
return,return-fromиgo, например. Emacs Lisp имеет толькоthrow. Библиотека cl-lib предоставляет версии некоторых из них. См. Блоки и выходы в Common Lisp Extensions.
catch устанавливает точку возврата для функции throw.
Точка возврата отличается от других таких точек возврата с помощью
tag, которая может быть любым объектом Lisp, кроме nil.
Аргумент tag вычисляется нормально до установления точки возврата.
При действии точки возврата catch вычисляет формы
body в текстовом порядке. Если формы выполняются нормально (без
ошибки или нелокального выхода) возвращается значение последней формы
тела из catch.
Если во время выполнения body выполняется throw,
указывающий то же самое значение tag, форма catch
немедленно выходит; возвращаемое ею значение — это то, что было указано
вторым аргументом throw.
Назначение throw — вернуться из точки возврата,
ранее установленной с помощью catch. Аргумент tag
используется для выбора среди различных существующих точек возврата; он должен
быть eq с значением, указанным в catch. Если
несколько точек возврата соответствуют tag, используется самая
внутренняя.
Аргумент value используется как значение, возвращаемое из
этого catch.
Если не существует точки возврата с тегом tag, то сигнализируется
ошибка no-catch с данными (tag
value).