Схема: как работает вложенный вызов / cc для сопрограммы?

Я смотрю на следующий пример сопрограммы из http://community.schemewiki.org/?call-with-current-continuation:

 (define (hefty-computation do-other-stuff) 
    (let loop ((n 5)) 
      (display "Hefty computation: ") 
      (display n) 
      (newline) 
      (set! do-other-stuff (call/cc do-other-stuff)) ; point A
      (display "Hefty computation (b)")  
      (newline) 
      (set! do-other-stuff (call/cc do-other-stuff)) 
      (display "Hefty computation (c)") 
      (newline) 
      (set! do-other-stuff (call/cc do-other-stuff)) 
      (if (> n 0) 
          (loop (- n 1))))) 

лишняя работа:

 ;; notionally displays a clock 
 (define (superfluous-computation do-other-stuff) 
    (let loop () 
      (for-each (lambda (graphic) 
                  (display graphic) 
                  (newline) 
                  (set! do-other-stuff (call/cc do-other-stuff))) 
                '("Straight up." "Quarter after." "Half past."  "Quarter til.")) ; point B
      (loop))) 


(hefty-computation superfluous-computation) 

Какой должен быть контекст при первом использовании call / cc? Когда я говорю о контексте, я имею в виду, куда мы должны «вернуться» в результате перехода callcc?

Насколько я понимаю, в первый раз, когда вы вызываете call / cc, do-other-stuff по сути становится процедурой, которая выполняет код избыточных вычислений, а затем переходит к точке сразу после набора! (точка А). Во второй раз он обернет свое поведение «переход к точке B» вокруг «перехода к точке A и выполнения контекста или любого другого кода, следующего за точкой A». Это верно?

Не похоже, что этот код будет работать, если установить! на самом деле произошло. Или это набор! необходимо для работы этого кода?

Визуальное представление того, что происходит, действительно поможет.


person hxngsun    schedule 12.11.2012    source источник


Ответы (1)


Контекст call/cc - это то место, откуда когда-либо вызывается call/cc. Вы можете почти представить call/cc подобный goto, который перемещает код обратно туда, где вы были раньше, и заменяет (call/cc whatever) возвращаемым значением.

call/cc в основном говорит: «Давайте сделаем эту функцию и отдадим ее, чтобы сразу вернуться сюда и забыть о том, что еще она делала»

Хорошо, когда я впервые пытался понять call/cc, я нашел этот код крайне запутанным, поэтому давайте посмотрим на упрощенный пример сопрограммы:

(define r1
  (lambda (cont)
    (display "I'm in r1!")
    (newline)
    (r1 (call/cc cont))))

(define r2
  (lambda (cont2)
    (display "I'm in r2!")
    (newline)
    (r2 (call/cc cont2))))

Хорошо, это точно такая же концепция, что и ваш код. Но все намного проще.

В этом случае, если мы вызовем (r1 r2), это напечатает

I'm in r1
I'm in r2
I'm in r1
I'm in r2    
I'm in r1
I'm in r2    
I'm in r1
I'm in r2
...

Почему? Поскольку r1 сначала принимает r2 как cont, поэтому он сообщает нам, что он находится в r1. И затем он рекурсивно повторяется с результатом (call/cc cont) или (call/cc r2).

Хорошо, так что из этого получится? ну (call/cc r2) начнет выполнение r2 и объявит, что он находится в r2, а затем выполнит рекурсию с результатом (call/cc cont2). Хорошо, так что же снова было cont2? cont2 был продолжением этого выражения ранее в r1. Поэтому, когда мы вызываем это здесь, мы передаем продолжение в то место, где мы сейчас находимся. Затем мы забываем все, что мы делали в r2, и возвращаемся к выполнению r1.

Это повторяется в r1 сейчас. Мы объявляем материал, а затем возвращаемся туда, где мы были раньше в r2, и наше выражение из предыдущего, (call/cc cont2) возвращает продолжение туда, где мы были в r1, а затем мы продолжаем наш веселый бесконечный цикл.

Вернуться к вашему коду

В вашем коде концепция точно такая же. Фактически, superfluous-computation почти идентичен приведенным выше функциям, если вы остановитесь и подумаете об этом. Так что с set!s? В этом коде все, что они делают, это изменяют значение do-other-work на самое новое продолжение. Вот и все. В моем примере я использовал рекурсию. В этом примере они используют set!.

person Daniel Gratzer    schedule 12.11.2012
comment
Когда вы говорите, что cont2 был продолжением этого выражения ранее в r1, вы говорите, что cont2 - это процедура, которая переходит обратно к строке r1 (call/cc cont), за исключением того, что (call/cc cont) заменяется процедурой, которая переходит к тому месту, где r2 собирается рекурсивно? У меня проблемы с пониманием того, что в итоге оценивает call/cc cont, прежде чем вы фактически закончите результат r1 (call/cont). - person hxngsun; 12.11.2012
comment
сказать "(cont2 val)" похоже на "вернуться к r1 (call/cc cont)" и заменить (call/cc cont) на val - person Daniel Gratzer; 12.11.2012
comment
На самом деле, когда я говорю «заменить», я имею в виду, что (call/cc cont) оценивается как val. Вы можете думать об этом как о замене, если это поможет - person Daniel Gratzer; 12.11.2012
comment
Итак, если я правильно понимаю, когда выполняется call / cc cont2, мы выполняем переход к (r1 val), где val является продолжением r2. Затем мы рекурсивно переходим в r1, и cont становится переходом туда, где r2 собирался рекурсивно вызывать себя? Большое спасибо за вашу помощь, я очень ценю это. Я потратил целый день, пытаясь понять, как все работает. - person hxngsun; 12.11.2012
comment
Да, это общая идея, это помогло мне тоже следовать вместе с двумя пальцами, 1 из которых выполнялся, а 1 был местом для следующего продолжения, чтобы тоже прыгнуть - person Daniel Gratzer; 12.11.2012