La recursión es una técnica útil, común en lenguajes funcionales y en Kotlin. Sin embargo, la recursión directa a menudo conduce a un desbordamiento de pila (StackOverflowError). Para combatir esto, muchos lenguajes han introducido optimizaciones de recursión de cola (tail call optimization). En Kotlin, esto se implementa explícitamente mediante la palabra clave tailrec.
La recursión convencional crea nuevos marcos de pila en cada llamada. Cuando la profundidad de la recursión es grande, esto lleva a un StackOverflowError. La optimización automática de la recursión de cola no siempre es posible, y la JVM no la admite como, por ejemplo, los compiladores de algunos otros lenguajes (Scala, Erlang).
Kotlin permite marcar una función con la palabra clave tailrec. Si la función es realmente una recursión de cola ("tail call" - la llamada resultante a sí misma es la operación final), el compilador la reemplaza por un bucle, lo que evita el desbordamiento de pila.
Ejemplo de código:
tailrec fun factorial(n: Int, acc: Int = 1): Int = if (n == 0) acc else factorial(n - 1, acc * n) println(factorial(5)) // 120
Características clave:
¿Qué sucederá si tailrec está presente, pero la llamada no está en la posición de cola?
El compilador producirá un error y no aplicará la optimización.
tailrec fun sum(n: Int, acc: Int = 0): Int { println(n) // después de esto no se puede optimizar return if (n == 0) acc else sum(n - 1, acc + n) } // Error de compilación: llamada no en la posición de cola
¿Se puede usar tailrec en funciones open o métodos abstractos?
No, la palabra clave solo funciona en funciones final.
open class Base { // Error: // tailrec open fun test() {} }
¿Hay diferencia en el rendimiento entre la recursión convencional y tailrec?
Sí, tailrec transforma la función en un bucle, reduciendo los costos de pila y eliminando StackOverflowError.
El desarrollador marca todas las funciones recursivas como tailrec sin pensar si la optimización puede aplicarse. Las llamadas no siempre están en la posición de cola; como resultado, los proyectos no se compilan y la recursión no funciona de manera efectiva.
Pros:
El desarrollador analiza la lógica, implementa recursión de cola en funciones como la búsqueda en un árbol o factorial. tailrec se usa donde es realmente posible. Surge un bucle compilado de manera efectiva sin desbordamiento de pila.
Pros: