Clojureは、単独で末尾呼び出しの最適化を実行しません。末尾再帰関数があり、それを最適化する場合は、特別な形式を使用する必要がありますrecur
。同様に、相互に再帰的な関数が2つある場合、を使用してのみ最適化できますtrampoline
。
Scalaコンパイラは、再帰関数に対してTCOを実行できますが、2つの相互再帰関数に対してはできません。
これらの制限について読んだときはいつでも、JVMモデルに固有の制限に常に起因していました。コンパイラについてはほとんど何も知りませんが、これは少し困惑します。から例を見てみましょうProgramming Scala
。ここで機能
def approximate(guess: Double): Double =
if (isGoodEnough(guess)) guess
else approximate(improve(guess))
に翻訳されています
0: aload_0
1: astore_3
2: aload_0
3: dload_1
4: invokevirtual #24; //Method isGoodEnough:(D)Z
7: ifeq
10: dload_1
11: dreturn
12: aload_0
13: dload_1
14: invokevirtual #27; //Method improve:(D)D
17: dstore_1
18: goto 2
そのため、バイトコードレベルでは、が必要goto
です。この場合、実際には、ハードワークはコンパイラーによって行われます。
基礎となる仮想マシンのどの機能により、コンパイラはTCOをより簡単に処理できますか?
サイドノートとして、私は実際のマシンがJVMよりもはるかに賢いとは思わないでしょう。それでも、Haskellなどのネイティブコードにコンパイルする多くの言語では、テールコールの最適化に問題はないようです(もちろん、Haskellは怠のために時々発生する可能性がありますが、それは別の問題です)。