Optimalizace koncové rekurze má svůj smysl, nejde jen o optimalizaci času, ale i o optimalizaci paměti. A tady se snadno můžeme dostat na o stupeň jinou asymptotickou složitost. Což už může být významné, a může to ovlivnit, jak si můžeme dovolit napsat kód. A v tomto mi přijde, že koncová rekurze je často to hlavní, a obecnější optimalizace koncového volání už nepřinese až o tolik navíc.
V tomto případě jsem rychle přečetl popis a přijde mi, že spíše než o optimalizaci koncového volání jde o interpret, který nějak využívá koncového volání.
> člověk ztrácí stack trace, takže ladění je těžší
Jo, to je jedna ze stinných stránek, a tuším i důvod, proč to nedělá automaticky třeba JVM. (Tuším, že byla nějaká iniciativa, která by umožnila označit invoke* instrukce, které lze takto optimalizovat, ale nevím o tom, že by to bylo dokončeno.) A I důvod, proč omezení na rekurzi může být dobrý trade-off.
> obvykle tail call trvá déle než normální call, protože se tam nějak manipuluje se zásobníkem
To by mě zajímalo. Měl jsem za to, že v podstatě místo přidání stack frame se poslední stačí frame přepíše. Jo, bude to vyžadovat nějakou opatrnost – naivně bych prvně nový stack frame vytvořil někde bokem, a pak teprve přepsal starý, protože při tvorbě nového stack frame potřebuju starý. (Představme si situaci, kdy funkce f(a, b) bude volat f(b, a).) Při kompilaci bych čekal, že si s tím poradí optimalizátor, při interpretaci je to trošku otázka, ale nečekal bych, že to bude mít zásadní roli.
On sa namiesto call pouzije jmp, takze existujuci stack frame sa zrecykluje (uz sa tam vraciat nebude, vsak to bolo posledne volanie v tele funkcie, aj v pripade a->b a b->a, pokial nemaju funkcie tolko argumentov, ze musia ist na stack) a az niekedy nastane ret, tak sa vrati tam, odkial sa povodne funkcia volala.