Что именно означает "приглашение к продолжению"?
Я пытаюсь расшифровать документацию
call-with-continuation-prompt
относится
proc
к данномуarg
s с текущим продолжением, расширенным приглашением. Приглашение помеченоprompt-tag
, который должен быть результатом либоdefault-continuation-prompt-tag
(по умолчанию) илиmake-continuation-prompt-tag
, Результатproc
является результатомcall-with-continuation-prompt
вызов.
Я понимаю ту часть, где говорится "Применяется proc
к данному arg
с текущим продолжением ", а потом это просто бред оттуда.
Что это вообще означает для продолжения, чтобы быть "расширенным", и как "подсказка" делает это "расширением"?
1 ответ
Что такое подсказка, концептуально?
Схема в целом имеет идею продолжения, но Ракет дополняет это идеей продолжения с разделителями. Идея продолжения состоит в том, что он захватывает оставшиеся вычисления, которые нужно оценить. Я не буду пытаться объяснить продолжения в целом, поскольку это выходит за рамки этого вопроса.
Однако я объясню, что делает продолжения с разделителями особенными. Обычно захват продолжения захватывает все вычисления вплоть до верхнего уровня. Это делает их использование относительно ограниченным для реализации сложных управляющих структур, поскольку применение продолжения полностью освобождает управление выполнением программы.
С продолжениями с разделителями вы можете захватить только определенную часть продолжения. Части оценки, которые фактически получены, ограничиваются приглашениями, которые действуют как маркеры вдоль текущего продолжения, которые определяют, сколько продолжений нужно захватить.
Хорошо, но что это значит?
Концепция разграниченных продолжений не совсем понятна, если не увидеть ее в действии по сравнению с неограниченными продолжениями.
Стандартные (без разделителей) продолжения
Рассмотрим следующий пример кода.
(define *k* #f)
(sqrt
(+ 1 2 3
(call/cc
(λ (k)
(set! *k* k)
0))))
Этот код очень прост - он захватывает продолжение и сохраняет глобальную привязку *k*
, Само продолжение выглядит так:
(sqrt (+ 1 2 3 _))
(Где _
представляет собой "дыру", которую нужно заполнить при вызове продолжения.)
Применение этого продолжения будет работать именно так, как и следовало ожидать.
> (*k* 3) ; evaluates (sqrt (+ 1 2 3 3))
3
Это все очень обыденно. Так в чем же разница, представленная продолжениями с разделителями?
Продолжения с разделителями
Что, если бы мы только хотели захватить часть продолжения в *k*
, Например, что если бы мы только хотели запечатлеть это продолжение?
(+ 1 2 3 _) ; the inner portion of the last continuation
Мы можем сделать это, установив приглашение продолжения, которое отрегулирует, сколько фактически будет получено продолжение.
(sqrt
(call-with-continuation-prompt
(λ ()
(+ 1 2 3
(call/cc
(λ (k)
(set! *k* k)
0))))))
Теперь, применяя *k*
дает внутренний результат:
> (*k* 3)
9
Аналогия для продолжения с разделителями
Продолжения могут быть несколько абстрактной концепцией, поэтому, если приведенный выше пример кода не совсем понятен, рассмотрите эту аналогию.
Модель оценки представляет собой стек - каждый вызов функции помещает новый кадр в стек и возвращается из функции, которая выводит кадр из стека. Мы можем визуализировать стек вызовов как стопку карт.
Обычно при захвате продолжения он захватывает текущий кадр и все кадры под ним, как показано ниже.
Верхний уровень, обозначенный синим цветом, не зафиксирован. По сути, это приглашение по умолчанию в системе с разделителями.
Однако установка нового приглашения создает своего рода прозрачный разделитель между кадрами, который влияет на то, какие кадры будут захвачены как часть продолжения.
Этот разделитель ограничивает степень продолжения.
Приложение: быстрые теги и барьеры продолжения
Это основы продолжений с разделителями, но есть и другие способы управления продолжениями, которые дают еще больше возможностей системе продолжения (а также защищают ее от вредоносного кода), и это быстрые теги и барьеры продолжения.
Идея тега приглашения по сути является "меткой", которая маркирует данное приглашение. Используя приведенную выше аналогию с картой, каждому прозрачному разделителю можно присвоить метку. Затем, когда вы захватываете продолжение, вы можете указать, чтобы захватить весь путь до этой конкретной метки, даже если между ними есть другие подсказки с другими метками.
С другой стороны, барьеры продолжения являются мерой безопасности. Как и подсказки, они могут быть визуализированы как "разделители", расположенные между элементами стека вызовов, но вместо того, чтобы использоваться в качестве меток для контроля того, какая часть стека захвачена, они служат в качестве защитных устройств, чтобы препятствовать тому, чтобы продолжения проходили "через" барьер.
Для получения более подробной информации об этом, прочитайте раздел в справочнике по ракетке о барьерах продолжения. Вот выдержка:
В частности, продолжение может быть заменено другим, только если замена не вводит никаких преград продолжения. Он может удалить барьеры продолжения только через переходы к продолжениям, которые являются хвостом текущего продолжения. Таким образом, барьер продолжения предотвращает "скачки вниз" в продолжение, защищенное барьером.