fluid-letを最適化できるケース
fluid-let自体の継続とbodyの継続はdynamic-environmentが異なる---それがfluid-letの目的---なので、R5RS以降のdynamic-envionmentを継続の一部とするセマンティクスでは末尾呼び出しではない。
一般にはもちろんそうなんですが、nmoshでは保存した動的環境を使わない特殊なケースが有って、しかも、それが重要なループに含まれているので最適化できるというのが前のエントリの意図なんです。。
fluid-let
nmosh(の元にしたexpander)のfluid-letは以下のように定義されている。
(define-syntax fluid-let (syntax-rules () ((fluid-let () be ...) (begin be ...)) ((fluid-let ((p0 e0) (p e) ...) be ...) (let ((saved p0)) (set! p0 e0) ; ← (A) (call-with-values (lambda () ; ← (B) (fluid-let ((p e) ...) be ...)) (lambda results (set! p0 saved) ; ← (C) (apply values results)))))))
これはGaucheのfluid-letマクロとちがって大域脱出を考慮していない。
順番に値を評価してsavedに保存し(A)、be(body)を実行後(B)、元に戻す(C)。このとき、A/Cで保存/設定した値にアクセスされないことが保証されているケースが有り、そのようなケースを検出してAやCを省略することでBを末尾位置にできることがある。
ケース1 : 末尾位置でのループ
このfluid-letが末尾位置にあって、大域脱出が無く、自分自身に再帰するならループの途中で保存された値にアクセスされることは無い。
例えば、
(define (check a) (fluid-let ((*REG* a)) (if a (check (do-something-with-*REG*)) 'OK)))
は、
(define (check a) (let ((saved *REG*)) (set! *REG* a) (let ((result (if a (check (do-something-with-*REG*)) ; ← ここは末尾位置でない 'OK))) (set! *REG* saved) result)))
のように展開される(ここでは多値は無視)。これは、checkを2つの手続きに分けて
(define (check-inner a) (set! *REG* a) (if a (check-inner (do-something-with-*REG*)) ; ← ここは末尾位置 'OK)) (define (check a) (let ((saved *REG*)) (set! *REG* a) (let ((result (if a (check-inner (do-something-with-*REG*)) 'OK))) (set! *REG* saved) result)))
のようにすることで大部分を末尾再帰呼び出しにできる。
このケースは新しいマクロを導入して手で修正することで一応実現できる。
ケース2 : 保持した値をすぐ上書きするケース
(fluid-let ((*VAL* a)) (fluid-let ((*VAL* b)) ...))
こんなケースは一見存在しないように思えるが、nmoshでは多用している。。