マクロでtarai回し
id:nozom:20060116#1137365062で載せたtarai関数版がそもそも間違っていた。
(define (tarai x y z) (if (< x y) y (tarai (tarai (1- x) y z) (tarai (1- y) z x) (tarai (1- z) x y))))
不等号は<ではなくて<=が正しい。このせいで、(1 1 1) (2 2 2)…などが自分自身に展開されてしまい、展開形にそれらを含むほとんどの数の組み合わせが無限ループしていた。
もう一つのバグはマクロの次の部分にあった。
; x >= y の場合 ((_ (%r1 ...) () (%x1 %x2 ...) (%y1 %y2 ...) (%z1 %z2 ...)) (tarai-mac (tarai-mac (%x2 ...) (%y1 %y2 ...) (%z1 %z2 ...)) (tarai-mac (%y2 ...) (%z1 %z2 ...) (%x1 %x2 ...)) (tarai-mac (%z2 ...) (%x1 %x2 ...) (%y1 %y2 ...))))
これは上手くいかない。関数の呼び出しは引数の評価が先に行われるが、マクロの場合は先頭から展開されるため、このような形の再帰呼び出しはマクロではできない。
ではどうするかというと、関数呼び出しで行われることを明示的に表せばいい。こういう場合はCPS(Continuation Passing Style)だ。
というわけで、taraiと等価なCPS関数は以下のようになる。
(define (tarai_cps x y z k) (if (<= x y) (k y) (tarai_cps (1- x) y z (lambda (v1) (tarai_cps (1- y) z x (lambda (v2) (tarai_cps (1- z) x y (lambda (v3) (tarai_cps v1 v2 v3 k)))))))))
(tarai_cps 12 6 1 values) ==> 12
これは次のようなコードと等価なので、
(define (tarai-cps x y z k) (if (<= x y) (k y) (tarai-cps-0 x y z k))) (define (tarai-cps-0 x y z k) (tarai-cps (1- x) y z (cut tarai-cps-1 x y z <> k))) (define (tarai-cps-1 x y z v1 k) (tarai-cps (1- y) z x (cut tarai-cps-2 x y z v1 <> k))) (define (tarai-cps-2 x y z v1 v2 k) (tarai-cps (1- z) x y (cut tarai-cps-3 x y z v1 v2 <> k))) (define (tarai-cps-3 x y z v1 v2 v3 k) (tarai-cps v1 v2 v3 k))
これを参考にしてマクロで書くと、
(define-syntax tarai-mac (syntax-rules () ((_ (%x ...) (%y ...) (%z ...)) (tarai-mac 0 (%x ...) (%y ...) (%z ...))) ((_ 0 %x %y %z %k ...) (tarai-mac "main" %x %y %x %y %z %k ...)) ((_ 1 %x %y %z %v1 %k ...) (tarai-mac "sub" %y %z %x (tarai 2 %x %y %z %v1) %k ...)) ((_ 2 %x %y %z %v1 %v2 %k ...) (tarai-mac "sub" %z %x %y (tarai 3 %x %y %z %v1 %v2) %k ...)) ((_ 3 %x %y %z %v1 %v2 %v3 %k ...) (tarai-mac 0 %v1 %v2 %v3 %k ...)) ((_ "sub" (%x1 %x2 ...) %y %z %k ...) (tarai-mac "main" (%x2 ...) %y (%x2 ...) %y %z %k ...)) ((_ "main" (%x1 %x2 ...) (%y1 %y2 ...) %x %y %z %k ...) (tarai-mac "main" (%x2 ...) (%y2 ...) %x %y %z %k ...)) ((_ "main" () (%y1 ...) %x %y %z) %y) ((_ "main" () (%y1 ...) %x %y %z (%k1 ...) %k2 ...) (%k1 ... %y %k2 ...)) ((_ "main" (%x1 %x2 ...) () %x %y %z %k ...) (tarai-mac "sub" %x %y %z (tarai 1 %x %y %z) %k ...)) ))
…元の形から結構変わってしまったが、一応ちゃんと動く。
(macroexpand '(tarai (() () ()) (() ()) (()))) ==> (() () ())
せっかく自分で評価順を決められるのだから、引数の評価を関数呼び出しの前ではなく後で行うようにしたい(専門的には、前者を「作用順序」、後者を「正規順序」と言うらしい)。
この続きはまた今度。