Recursie is een nuttige techniek die voorkomt in functionele talen en in Kotlin. Echter, directe recursie leidt vaak tot een stapeloverloop (StackOverflowError). Om dit te bestrijden is er in veel talen een optimalisatie van staartrecursie (tail call optimization) ingevoerd. In Kotlin is dit expliciet geïmplementeerd met het sleutelwoord tailrec.
Gewone recursie creëert nieuwe stapelframes bij elke aanroep. Wanneer de diepte van de recursie groot is, leidt dit tot een StackOverflowError. Automatische optimalisatie van staartrecursie is niet altijd mogelijk, en de JVM ondersteunt dit niet zoals bijvoorbeeld de compilers van sommige andere talen (Scala, Erlang).
Kotlin stelt je in staat om een functie te markeren met het sleutelwoord tailrec. Als de functie daadwerkelijk een staartrecursie is ("tail call" — de resultaat-aanroep naar zichzelf is de laatste bewerking), vervangt de compiler deze door een lus, waardoor de stapeloverloop wordt voorkomen.
Voorbeeldcode:
tailrec fun factorial(n: Int, acc: Int = 1): Int = if (n == 0) acc else factorial(n - 1, acc * n) println(factorial(5)) // 120
Kernkenmerken:
Wat gebeurt er als tailrec is gebruikt, maar de aanroep zich niet in een staartpositie bevindt?
De compiler geeft een foutmelding en past de optimalisatie niet toe.
tailrec fun sum(n: Int, acc: Int = 0): Int { println(n) // hierna kan niet worden geoptimaliseerd return if (n == 0) acc else sum(n - 1, acc + n) } // Compilatiefout: aanroep niet in staartpositie
Kan tailrec worden gebruikt in open functies of abstracte methoden?
Nee, het sleutelwoord werkt alleen voor final functies.
open class Base { // Fout: // tailrec open fun test() {} }
Is er een verschil in prestaties tussen gewone recursie en tailrec?
Ja, tailrec verandert de functie in een lus, vermindert de overhead van de stapel en voorkomt StackOverflowError.
Een ontwikkelaar markeert alle recursieve functies met tailrec zonder na te denken of de optimalisatie kan worden toegepast. Aanroepen staan niet altijd in staartpositie — uiteindelijk compileren de projecten niet en werkt de recursie niet efficiënt.
Voordelen:
Een ontwikkelaar analyseert de logica, past staartrecursie toe in functies zoals zoeken in bomen of het berekenen van faculteiten. tailrec wordt gebruikt waar het echt mogelijk is. Er ontstaat een efficiënt gecompileerde lus zonder stapeloverloop.
Voordelen: