traverse puzzle とは?

こちらで触れられている “Olivier Danvy’s puzzle” なのですけれども、なぜそうなるのか全くわからない。

;; racket
(require racket/control)

(let ()
  (define (traverse xs)
    (define (visit xs)
      (if (null? xs)
        '()
        (visit (shift k (cons (car xs) (k (cdr xs)))))))

    (reset (visit xs)))

  (traverse '(a b c)))
;;=> '(a b c)

(let ()
 (define (traverse xs)
   (define (visit xs)
     (if (null? xs)
       '()
       (visit (control k (cons (car xs) (k (cdr xs)))))))

   (prompt (visit xs)))

 (traverse '(a b c)))
;;=> '(c b a)

これらを見るとようやく、何がわからなかったのか、というのが把握できました。そもそも reset/shiftprompt/control はどういうもので何が違うのかを全く理解していなかった、というオチでした。限定継続オペレータを俯瞰できるので、それぞれどんな違いがあるのかを一望できます。(レポートのリファレンスを更に辿るのも良い)

というわけで、一歩一歩トレースしてみます。

shiftcontrol が来たら、継続を関数で表現しようとしてみて、続くフォームの評価をどんな継続へ渡してゆくのかを考えてゆく、ということにしました。

まずは、 reset/shift について。

(reset (shift k (cons 1 (k #f)))
       (shift k (cons 2 (k #f)))
       '())
;; 評価
(reset _(shift k (cons 1 (k #f)))_
       (shift k (cons 2 (k #f)))
       '())
;; 継続 #0#
(lambda (x)
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   ((lambda (v)
      v
      (shift k (cons 2 (k #f)))
      '())
    x)))
;; 評価 shift の継続
((lambda (v)
   (reset v))
 _(cons 1 (#0# #f))_)
;; 評価
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  _(#0# #f)_))
;; 評価 #0# 展開
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  _((lambda (x)
      ((lambda (v)
         ;; shift は reset を付ける #0#
         (reset v))
       ((lambda (v)
          v
          (shift k (cons 2 (k #f)))
          '())
        x)))
    #f)_))
;; 評価
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   ((lambda (v)
      v
      _(shift k (cons 2 (k #f)))_
      '())
    #f))))
;; 継続 #1#
(lambda (x)
  ((lambda (v)
     ;; shift は reset を付ける #1#
     (reset v))
   ((lambda (v)
      v
      '())
    x)))
;; 評価 shift の継続
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   _(cons 2 (#1# #f))_)))
;; 評価
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   ((lambda (v)
      (cons 2 v))
    _(#1# #f)_))))
;; 評価 #1# 展開
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   ((lambda (v)
      (cons 2 v))
    _((lambda (x)
        ((lambda (v)
           ;; shift は reset を付ける #1#
           (reset v))
         ((lambda (v)
            v
            '())
          x)))
      #f)_))))
;; 評価
((lambda (v)
   (reset v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; shift は reset を付ける #0#
     (reset v))
   ((lambda (v)
      (cons 2 v))
    ((lambda (v)
       ;; shift は reset を付ける #1#
       (reset v))
     ((lambda (v)
        v
        '())
      #f))))))
;;=> '(1 2)

次に、 prompt/control について。

(prompt (control k (cons 1 (k #f)))
        (control k (cons 2 (k #f)))
        '())
;; 評価
(prompt _(control k (cons 1 (k #f))_)
        (control k (cons 2 (k #f)))
        '())
;; 継続 #0#
(lambda (x)
  ((lambda (v)
     ;; control は prompt を付けない #0#
     v
     (control k (cons 2 (k #f)))
     '())
   x))
;; 評価 control の継続
((lambda (v)
   (prompt v))
 _(cons 1 (#0# #f))_)
;; 評価
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 1 v))
  _(#0# #f)_))
;; 評価 #0# 展開
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 1 v))
  _((lambda (x)
      ((lambda (v)
         ;; control は prompt を付けない #0#
         v
         (control k (cons 2 (k #f)))
         '())
       x))
    #f)_))
;; 評価
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 1 v))
  ((lambda (v)
     ;; control は prompt を付けない #0#
     v
     _(control k (cons 2 (k #f)))_
     '())
   #f)))
;; 継続 #1#
(lambda (x)
  ;; control は prompt を付けない #1#
  ((lambda (v)
     (cons 1 v))
   ((lambda (v)
      v
      '())
    x)))
;; 評価 control の継続
((lambda (v)
   (prompt v))
 _(cons 2 (#1# #f))_)
;; 評価
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 2 v))
  _(#1# #f)_))
;; 継続 #1# 展開
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 2 v))
  _((lambda (x)
      ;; control は prompt を付けない #1#
      ((lambda (v)
         (cons 1 v))
       ((lambda (v)
          v
          '())
        x)))
    #f)_))
;; 評価
((lambda (v)
   (prompt v))
 ((lambda (v)
    (cons 2 v))
  ((lambda (v)
     (cons 1 v))
   ((lambda (v)
      v
      '())
    #f))))
;;=> '(2 1)

このように、 shift では reset を付けるので、この調子で継続を表現しようとしていくと、関数がどんどん深くなっていきます。これに対して control では prompt を付けないので、継続を表す関数はその都度再計算されます(いや、継続は、その都度計算される、というのは共通しているので、表現が何かおかしいのだけれども( prompt までの継続が毎度 k に束縛されるので当然なのだけれども))。

字面が継続渡し形式に似ているのに気付いた。ので、まとめてみます。(継続渡し形式についてのメモはこちら 直観に反する? — https://t.laafc.net/2018/04/19/counterintuitivep.html)

(require racket/control
         srfi/1)

(let ()
  (define (dup xs)
    (fold-right cons '() xs))
  (define (rev xs)
    (fold cons '() xs))

  (define (dup-k xs)
    (define (recur xs k)
      (if (null? xs)
        (k '())
        (recur (cdr xs) (lambda (r) (k (cons (car xs) r))))))

    (recur xs values))
  (define (rev-k xs)
    (define (recur xs k)
      (if (null? xs)
        (k '())
        (recur (cdr xs) (lambda (r) (cons (car xs) (k r))))))

    (recur xs values))

  (define (dup-c xs)
    (define (visit xs)
      (if (null? xs)
        '()
        (visit (shift k (cons (car xs) (k (cdr xs)))))))

    (reset (visit xs)))
  (define (rev-c xs)
    (define (visit xs)
      (if (null? xs)
        '()
        (visit (control k (cons (car xs) (k (cdr xs)))))))

    (prompt (visit xs)))

  (let* ((xs '(a b c))
         (ys (reverse xs)))
    (values
     (list= equal? xs (dup xs) (dup-k xs) (dup-c xs))
     (list= equal? ys (rev xs) (rev-k xs) (rev-c xs)))))
;;=> #t, #t