マクロでtarai回し 2回目

また今度と言ってから8時間しか経ってないけど、どうやらできたみたいなので公開。

まず、普通の関数で引数の評価を遅らせることを考えてみる(r/oはregular order=正規順序の意味)。

(define (tarai-r/o x y z)
  (let ((x-value (force x))
        (y-value (force y)))
    (if (<= x-value y-value)
        y-value
        (let ((z-value (force z)))
          (tarai-r/o (delay (tarai-r/o (1- x-value) y z))
                     (delay (tarai-r/o (1- y-value) z x))
                     (delay (tarai-r/o (1- z-value) x y)))))))

そうしたら、例によってこれをCPSに変換する。

(define (tarai-r/o-cps-0 x y z k)
  (if (promise? x)
      (force-cps-0 x (cut tarai-r/o-cps-1 x y z <> k))
      (tarai-r/o-cps-1 x y z x k)))

(define (tarai-r/o-cps-1 x y z x-value k)
  (if (promise? y)
      (force_cps y (cut tarai-r/o-cps-2 x y z x-value <> k))
      (tarai-r/o-cps-2 x y z x-value y k)))

(define (tarai-r/o-cps-2 x y z x-value y-value k)
  (if (<= x-value y-value)
      (k y-value)
      (tarai-r/o-cps-3 x y z x-value y-value k)))

(define (tarai-r/o-cps-3 x y z x-value y-value k)
  (if (promise? z)
      (force_cps z (cut tarai-r/o-cps-4 x y z x-value y-value <> k))
      (tarai-r/o-cps-4 x y z x-value y-value <> k)))

(define (tarai-r/o-cps-4 x y z x-value y-value z-value k)
  (tarai-r/o-cps-0 (delay (tarai-r/o-cps-0 (1- x-value) y z <>))
                   (delay (tarai-r/o-cps-0 (1- y-value) z x <>))
                   (delay (tarai-r/o-cps-0 (1- z-value) x y <>))
                 k))

ここまでくれば、あとはもうマクロに変換するだけ。

(define-syntax tarai-r/o
  (syntax-rules ()
    ((_ "call" %x %y %z)
     (tarai-r/o 0 %x %y %z))

    ((_ 0 ("delay" %x) %y %z %k ...)
     (tarai-r/o "force" %x (tarai-r/o 1 %y %z) %k ...))

    ((_ 0 %x %y %z %k ...)
     (tarai-r/o 1 %y %z %x %k ...))

    ((_ 1 ("delay" %y) %z %x-value %k ...)
     (tarai-r/o "force" %y (tarai-r/o 2 %z %x-value) %k ...))

    ((_ 1 %y %z %x-value %k ...)
     (tarai-r/o 2 %z %x-value %y %k ...))

    ((_ 2 %z %x-value %y-value %k ...)
     (tarai-r/o "loop" %x-value %y-value %z %x-value %y-value %k ...))

    ((_ "loop" (%x1 %x2 ...) (%y1 %y2 ...) %z %x-value %y-value %k ...)
     (tarai-r/o "loop" (%x2 ...) (%y2 ...) %z %x-value %y-value %k ...))

    ((_ "loop" () (%y1 ...) %z %x-value %y-value %k ...)
     (tarai-r/o "return" %y-value %k ...))

    ((_ "loop" (%x1 %x2 ...) () %z %x-value %y-value %k ...)
     (tarai-r/o 3 %z %x-value %y-value %k ...))

    ((_ 3 ("delay" %z) %x-value %y-value %k ...)
     (tarai-r/o "force" %z (tarai-r/o 4 %x-value %y-value) %k ...))

    ((_ 3 %z %x-value %y-value %k ...)
     (tarai-r/o 4 %x-value %y-value %z %k ...))

    ((_ 4 (%x1 %x2 ...) (%y1 %y2 ...) (%z1 %z2 ...) %k ...)
     (tarai-r/o 0
                ("delay" (tarai-r/o 0 (%x2 ...) (%y1 %y2 ...) (%z1 %z2 ...)))
                ("delay" (tarai-r/o 0 (%y2 ...) (%z1 %z2 ...) (%x1 %x2 ...)))
                ("delay" (tarai-r/o 0 (%z2 ...) (%x1 %x2 ...) (%y1 %y2 ...)))
                %k ...))

    ((_ "return" %val)
     %val)

    ((_ "return" %val (%k1 ...) %k2 ...)
     (tarai-r/o "force" (%k1 ... %val) %k2 ...))

    ((_ "force" (%f ...) %k ...)
     (%f ... %k ...))

    ))

ちょっとしたトリックとして、継続スタックを自前で操作することによってマクロの中で自分自身を呼んでその戻り値を得ることができる。まあ、CPSをベースにしたんだからそう難しいことじゃないんだけど。

動作確認:

(macroexpand '(tarai-r/o "call" (() () () () () ()) (() ()) (())))
==> (() () () () () ())

引数の完全な評価を行っていないので、前の版よりも多少は高速化されているはず。