Может ли кто-нибудь объяснить call/cc простыми словами?

Я изучаю языковую ракетку и пытаюсь понять, для чего на самом деле предназначен call/cc. Может ли кто-нибудь объяснить это простыми словами и привести пример или два? Спасибо.

3 ответов


если у вас есть выражение (+ (* 2 3) (/ 10 2)) система схемы не будет оценивать все одновременно, но по частям. Порядок не указан в схеме, но давайте представим, что он слева направо (я думаю, что ракетка всегда делает слева направо):

ты (* 2 3), продолжением этого было бы вычислить (/ 10 2), потом (+ result1 result2). Способ, которым система схемы может это сделать, - преобразовать ваш код в продолжение прохождения стиле перед казнью. Выражение вверху получается что-то вроде этого:

 (lambda (k)
  (*& 2 
      3
      (lambda (result1)
        (/& 10
            2
            (lambda (result2)
              (+& result1 result2 k))))))

теперь, процедуры с & в конце концов такие же, как в схеме, за исключением того, что он принимает продолжение в качестве последнего аргумента. Пример одного из них: (define (+& a b k) (k (+ a b))). Все остальные делаются точно так же и считаются примитивными.

если вы примените это и передают display или values он будет отображаться или оценивать до 11. Однако, если вы использовали call/cc можно переопределить. Представьте себе это вариант:

(call/cc (lambda (k) (+ (* 2 3) (/ 10 (if (zero? a) (k +inf.0) a))

в схеме вы получаете ошибку при делении на ноль, и если это произойдет, вы хотите отменить остальные вычисления и сказать, что результат бесконечен. Код выше делает это, и если a равно нулю, это не будет суммировать результаты двух предыдущих вычислений.. Он фактически действует как Гото. Версия CPS этого кода будет выглядеть примерно так:

(lambda (k)
   (*& 2 
       3
       (lambda (result1)
         (zero?& a 
                 (lambda (azero?)
                   (if azero?
                       (k +inf.0) ; continuation used here
                       (/& 10
                           a
                           (lambda (result2)
                             (+& result1 result2 k))))))))) ; and here

так что call/cc делать? Ну, это позволяет вам кодировать ваш обычный способ, а не CPS например, как компьютер делает фактические вычисления, но вы получаете лучшее из двух миров, получив продолжение, так что вы можете сделать то же самое, как если бы вы написали его в CPS.

теперь представьте себе этот блок кода:

(let* ((c 10)
       (r (call/cc (lambda (exit) exit))))
  (display "Hello\n")
  (cond ((zero? c) 'done)
        (else (set! c (- c 1))
              (r r))))

здесь вы видите, что я возвращаю продолжение как r. Продолжение-это остальные вычисления, начинающиеся с установки r, тогда как display ... Это фактически отображает "hello\n" 11 раз с момента вызова продолжение в низ он делает то же самое снова и снова.

как eval Я стараюсь держать это на абсолютном минимуме, и когда я это делаю, я обычно делаю это, чтобы получить аборт от текущих вычислений. Например.

(define (lstadd1 lst)
  (call/cc (lambda (exit)
    (let loop ((lst lst))
       (cond ((pair? lst) (cons (add1 (car lst)) (loop (cdr lst))))
             ((null? lst) '())
             (else (exit #f))))))) ; not proper 

(lstadd1 '(1 2 3))   ; ==> (2 3 4)
(lstadd1 '(1 2 . 3)) ; ==> #f

для более примеров я люблю Мэтт Mights страница С большим количеством примеров использования продолжений.


не все реализации call/cc точно такие же, но, надеюсь, этот ответ может применяться ко всем общим вариациям, включая ракетку с небольшими проблемами. Эта история на самом деле основана на c строение из Unlambda.

метафора для вызова / cc

вы археолог супергероя на раскопках на старом месте Майя. Вы обнаруживаете изысканно построенный, прекрасно сохранившийся каменный дверной проем в элегантной арке. Но это просто дверной проем, лежащий на боку; рядом с ним нет никакой стены, и кажется, что он никогда не был частью стены. Ваш персонал получает от него энергию, поэтому вы транспортируете его в целости и сохранности в лабораторию для изучения.

4:17 вечера

после прогулки проходя под аркой, вы, к своему удивлению, замечаете, что держите в руке кубическую коробку, достаточно большую, чтобы поместить в нее книгу, с крышкой и единственной светящейся кнопкой. Вы не помните, как подобрали такую коробку, или видели ее раньше. Позвони своему помощнику и спроси, откуда коробка. Помощник не знает. Если опустить коробку, кнопка перестанет светиться. Вы поднимаете его снова, кнопка снова светится. Он светится только тогда, когда вы его держите; если помощник поднимает коробку, он это делает не светиться. В шутку вы кладете скрепку в коробку, закрываете крышку и нажимаете кнопку.

4:23 вечера

"нет-нет! А что случилось с коробкой?- говорит помощник.

размещение коробки #3 и примечание внутри коробки #2, затем нажмите кнопку на коробке #2.

2:01 АМ

выходя из арки вы держите пустую коробку, которая была помечена #3 - use to remember, и записку, написанную твоим почерком. Время намного позже, чем ожидалось 5: 31 вечера, поэтому вы не продолжаете с вашим предполагаемым планом взлома. Следуя вашим инструкциям, вы сами проходите через арку снова, получая новый ящик, который вы называете #4 - use to forget. Вы звоните в полицию, как было указано в записке, не зная почему, и слышите в новостях, что международная шпионская сеть распалась в вашем родном городе.


этот текст класс Я использую в своих лекциях о продолжений. Первоначально он был основан на Плай, но значительно расширен с более практичным кодом, а также включает в себя некоторые примеры со страницы Мэтью май. (Короче говоря, многие люди внесли в это косвенный вклад. Она не очень короткая, но ее легко прочитать.

возможно, однажды это позволит опубликовать это в качестве ответа, но на данный момент нет способа поместить столько текста здесь.