Lispインタープリター「Liyad」における末尾再帰最適化
拙作のLispインタープリター「Liyad」においても、 (無いとLisp処理系として格好が付かないので) 非常に限定的に末尾再帰最適化を実装しています。
Liyadにおける末尾再帰最適化
Liyadでは、単純にASTのS式が特定のパターン(1種類)に合致するときのみ末尾再帰最適化を行い、 関数の再帰呼び出しをループに展開します。
パターンの疑似コード:
($lambda (f-arg-1 ... f-arg-N) expr-a-1 ... expr-a-M ($if cond-expr expr-t ($self r-arg-expr-1 ... r-arg-expr-N) ))
これを次のようにASTを変換します。
疑似コード:
($lambda (f-arg-1 ... f-arg-N) ($until cond-expr ($clisp-let (tempsym-1 ... tempsym-N) expr-a-1 ... expr-a-M ($set tempsym-1 r-arg-expr-1) ... ($set tempsym-N r-arg-expr-N) ($set f-arg-1 tempsym-1) ... ($set f-arg-N tempsym-N) )) expr-a-1 ... expr-a-M expr-t )
手抜きですね。
一般的な処理系における末尾再帰最適化
当たり前ですが、if
以外の条件文(switch
等)でも最適化します。
また、静的に決定できるならば、関数のシンボル名に関係なく最適化してくれることでしょう (Liyadでは $self
という名前で参照していなければ最適化しません)。
関数内の複数のコードパスのうち、1つ以上の末尾が自身の呼び出しであれば適用できます。
また頭の良いコンパイラならば、2つの関数が相互に呼び合っている場合も最適化します。
処理系によっては、何かしらの形 (キーワードやデコレーターの類) で明示的に指示する必要があります。
参考
- Pythonで末尾再帰最適化
- 末尾再帰最適化の正体
- 関数型電卓プログラム fncalc の作成 (4) ←ジオシティーズなので2019/3以降アクセスできなくなります。