Schemeはご承知の通りループの構文がなくて、末尾再帰でループを実現する。
よく使うのはnamed letという構文で、例えば階乗の計算は
(let fact ((n 10)) |
てな具合になる。簡単な実装法としては、named letはマクロによって変換されて、単純なラムダ式になる:
((lambda (fact) |
関数が自分自身を参照するためにローカル変数fact
への代入が必要となっている。
しかしこの方法は(まともな処理系では最適化されるのかわからないけど、僕の参考にしている3impのスタックベースの)処理系の実装から考えるとあまりよろしくない。set!
による代入を使うと、そのローカル変数はボックス化する必要があって、ヒープの確保が発生してしまう。なぜかというと、どこかから継続で使われる可能性があるので、スタック上に持っておくことができなくなるからである。しかしヒープの確保はコストがかかるのでできれば避けたい。
でヒープを使わないループの実現方法を考えたんだけど、関数に自分自身を引数に追加してしまうのはどうかと思いついた。
(let1 fact (lambda (self n) |
これだとset!
を使わないのでヒープの確保が発生しないので、軽くなるはず。
問題は、手書きで (fact fact n)
などと書きたくないので、自動的に関数への引数を挿入する方法をどうするか。例えばmacrolet
のように局所的なマクロを定義できれば機械的にいけそうである。vallog: gauche に macroletを参考に、オレオレnamed-let
マクロを
(define-macro (macrolet letargs . body) |
のように定義してやると、
(named-let fact ((n 10)) |
と素直に書けば自動的に変換できて、しかもヒープ確保なしで動く。やったね!
こういう変換をラムダリフティングというのかな。