Clojure nie wykonuje optymalizacji ogona na własną rękę: jeśli masz funkcję rekurencji ogona i chcesz ją zoptymalizować, musisz użyć specjalnej formy recur
. Podobnie, jeśli masz dwie wzajemnie rekurencyjne funkcje, możesz je zoptymalizować tylko za pomocą trampoline
.
Kompilator Scala jest w stanie wykonać TCO dla funkcji rekurencyjnej, ale nie dla dwóch wzajemnie rekurencyjnych funkcji.
Ilekroć czytałem o tych ograniczeniach, zawsze przypisywano im pewne ograniczenia właściwe dla modelu JVM. Prawie nic nie wiem o kompilatorach, ale to mnie trochę zastanawia. Pozwól mi wziąć przykład z Programming Scala
. Tutaj funkcja
def approximate(guess: Double): Double =
if (isGoodEnough(guess)) guess
else approximate(improve(guess))
jest przetłumaczone na
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
Tak więc na poziomie kodu bajtowego wystarczy goto
. W tym przypadku kompilator wykonuje ciężką pracę.
Jakie narzędzie bazowej maszyny wirtualnej pozwoliłoby kompilatorowi łatwiej obsługiwać TCO?
Na marginesie, nie spodziewałbym się, że rzeczywiste maszyny będą znacznie mądrzejsze niż JVM. Mimo to wiele języków kompilujących się do kodu natywnego, takich jak Haskell, nie wydaje się mieć problemów z optymalizacją wywołań ogonowych (no cóż, Haskell może czasami mieć z powodu lenistwa, ale to już inna kwestia).