継続渡しとコンパイル(2)〜末尾再帰の場合

前のエントリでループの度にヒープにクロージャを生成しているのが無駄だと思う人がいるかもしれないが、それは例に挙げた関数が末尾再帰になっていないからだ。継続渡しは末尾再帰の時に最も真価を発揮する。
というわけで階乗計算の末尾再帰版。

(define (fact_tailrec n)
  (define (fact-aux n r)
    (if (> n 1)
        (fact-aux (1- n) (* n r))
        r))
  (fact-aux n 1))

これを継続渡しスタイルにしたものがこれ。

(define (fact_tailrec_cps n k)
  (define (fact-aux n r k)
    (if (> n 1)
        (fact-aux (1- n) (* n r) k)
        (k r)))
  (fact-aux n 1 k))

さらにこれを前のエントリと同様の規則で変換すると、

fact_tailrec_cps:
        (r1, r2, r3) := (r1, 1, r2)
        jmp fact-aux

fact-aux:
        r0 := r1 - 1
        if (r0 <= 0) jmp L1
        r0 := r1 - 1
        r4 := r0
        r0 := r1 * r2
        r5 := r0
        (r1, r2, r3) := (r4, r5, r3)
        jmp fact-aux

L1:
        (r1) := (r2)
        jmp r3

なんと、このように本物のループになってしまった(大げさ)。
ということで、継続渡しスタイルは効率が悪いというのは正しくない。