私は Scheme インタプリタを書こうとしていますが、TCO の実装が難しいと感じています。TCO が機能するために関数にどのようなプロパティが必要かはわかりません。
1) 定義の最後に自己再帰呼び出しがある関数:
(define (sum-list list accum)
(if (null list) accum
(sum-list (cdr list) (+ accum 1))))
2) 定義の最後にない自己再帰呼び出しを持つ関数:
(define (sum-list list accum)
(cond ((not (null list))
(sum-list (cdr list) (+ accum 1)))
(else accum)))
3) 再帰呼び出しを変数に格納してから返す関数:
(define (sum-list list accum)
(let ((sum
(if (null list)
accum
(sum-list (cdr list (+ accum 1))))))
sum))
4) 相互再帰関数:
(define (sum-list list accum)
(if (null list)
accum
(other-function (cdr list) (+ accum 1))))
(define (other-function list accum)
(sum-list list accum))
5) 関数定義の最後で別の無関係な関数を単純に呼び出す:
(define (foo)
(bar))
6) 私が考えることができる最もトリッキーなケースは閉鎖です。スコープ内の変数への参照を保持するとどうなりますか?
(define (sum-list list accum ignored)
(let ((local-var 12345))
(if (null list)
accum
(sum-list
(cdr list)
(+ accum 1)
(lambda () local-var)))))
7) 引数として自己再帰呼び出しを使用して、関数定義の最後で別の関数を呼び出す:
(define (sum-list list)
(if (null list)
0
(+ 1 (sum-list (cdr list)))))
私が理解しているように、TCO 実装 (Scheme と Common Lisp の両方) は TCO に適した関数を書き換えるため、TCO に適した関数を静的に検出できなければなりません。
私が知りたいのは:
- これらの関数のうち、TCO によって書き直されるのはどれですか? また、なぜそれらだけを書き直すのでしょうか?
- TCO の書き換えが発生する状況 (例: 7) ではありますが、関数は引き続きメモリを線形に消費しますか?